PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 2.2.8
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v2.2.8
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
imagify / assets / js / imagify-gulp.js

imagify-gulp.js in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF 2.2.8, at assets/js/imagify-gulp.js

401 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Library that handles the bulk optimization processes.
3 *
4 * @requires jQuery
5 */
6 window.imagify = window.imagify || {};
7
8 /* eslint-disable no-underscore-dangle, consistent-this */
9 (function($, d, w) {
10
11 /**
12 * Construct the optimizer.
13 *
14 * @param {object} settings {
15 * Optimizer settings:
16 *
17 * @type {string} groupID Group ID, like 'library' or 'custom-folders'.
18 * @type {string} context Context within this group, like 'wp' or 'custom-folders' (yes, again).
19 * @type {int} level Optimization level: 0 to 2.
20 * @type {int} bufferSize Number of parallel optimizations: usually 4.
21 * @type {string} ajaxUrl URL to request to optimize.
22 * @type {object} files Files to optimize: media ID as key (prefixed with an underscore), file URL as value.
23 * @type {string} defaultThumb A default thumbnail URL.
24 * @type {string} doneEvent Name of the event to listen to know when optimizations end.
25 * @type {array} imageExtensions A list of supported image extensions (only images).
26 * }
27 */
28 w.imagify.Optimizer = function ( settings ) {
29 // Settings.
30 this.groupID = settings.groupID;
31 this.context = settings.context;
32 this.level = settings.level;
33 this.bufferSize = settings.bufferSize || 1;
34 this.ajaxUrl = settings.ajaxUrl;
35 this.files = settings.files;
36 this.defaultThumb = settings.defaultThumb;
37 this.doneEvent = settings.doneEvent;
38
39 if ( settings.imageExtensions ) {
40 this.imageExtensions = settings.imageExtensions;
41 } else {
42 this.imageExtensions = [ 'jpg', 'jpeg', 'jpe', 'png', 'gif' ];
43 }
44
45 /**
46 * An array of media IDs (prefixed with an underscore).
47 */
48 this.prefixedMediaIDs = Object.keys( this.files );
49 /**
50 * An array of medias currently being optimized: {
51 * @type {int} mediaID The media ID.
52 * @type {string} filename The file name.
53 * @type {string} thumbnail The file thumbnail URL.
54 * }
55 */
56 this.currentItems = [];
57
58 // Internal counters.
59 this.totalMedia = this.prefixedMediaIDs.length;
60 this.processedMedia = 0;
61
62 // Global stats.
63 this.globalOriginalSize = 0;
64 this.globalOptimizedSize = 0;
65 this.globalGain = 0;
66 this.globalPercent = 0;
67
68 // Callbacks.
69 this._before = function () {};
70 this._each = function () {};
71 this._done = function () {};
72
73 // Listen to the "optimization done" event.
74 if ( this.totalMedia && this.doneEvent ) {
75 $( w ).on( this.doneEvent, { _this: this }, this.processedCallback );
76 }
77 };
78
79 /**
80 * Callback to trigger before each media optimization.
81 *
82 * @param {callable} fnc A callback.
83 * @return this
84 */
85 w.imagify.Optimizer.prototype.before = function( fnc ) {
86 this._before = fnc;
87 return this;
88 };
89
90 /**
91 * Callback to trigger after each media optimization.
92 *
93 * @param {callable} fnc A callback.
94 * @return this
95 */
96 w.imagify.Optimizer.prototype.each = function( fnc ) {
97 this._each = fnc;
98 return this;
99 };
100
101 /**
102 * Callback to trigger all media optimizations have been done.
103 *
104 * @param {callable} fnc A callback.
105 * @return this
106 */
107 w.imagify.Optimizer.prototype.done = function( fnc ) {
108 this._done = fnc;
109 return this;
110 };
111
112 /**
113 * Launch optimizations.
114 *
115 * @return this
116 */
117 w.imagify.Optimizer.prototype.run = function() {
118 var chunkLength = this.prefixedMediaIDs.length > this.bufferSize ? this.bufferSize : this.prefixedMediaIDs.length,
119 i;
120
121 for ( i = 0; i < chunkLength; i++ ) {
122 this.processNext();
123 }
124
125 return this;
126 };
127
128 /**
129 * Launch next optimization.
130 *
131 * @return this
132 */
133 w.imagify.Optimizer.prototype.processNext = function() {
134 if ( this.prefixedMediaIDs.length ) {
135 this.process( this.prefixedMediaIDs.shift() );
136 }
137
138 return this;
139 };
140
141 /**
142 * Launch an optimization.
143 *
144 * @param {string} prefixedId A media ID, prefixed with an underscore.
145 * @return this
146 */
147 w.imagify.Optimizer.prototype.process = function( prefixedId ) {
148 var _this = this,
149 fileURL = this.files[ prefixedId ],
150 data = {
151 mediaID: parseInt( prefixedId.toString().substr( 1 ), 10 ),
152 filename: this.files[ prefixedId ].split( '/' ).pop(),
153 thumbnail: this.defaultThumb
154 },
155 extension = data.filename.split( '.' ).pop().toLowerCase(),
156 regexp = new RegExp( '^' + this.imageExtensions.join( '|' ).toLowerCase() + '$' ),
157 image;
158
159 if ( ! extension.match( regexp ) ) {
160 // Not an image.
161 this.currentItems.push( data );
162 this._before( data );
163 this.send( data );
164 return this;
165 }
166
167 // Create a thumbnail and send the ajax request.
168 image = new Image();
169
170 image.onerror = function () {
171 _this.currentItems.push( data );
172 _this._before( data );
173 _this.send( data );
174 };
175
176 image.onload = function () {
177 var maxWidth = 33,
178 maxHeight = 33,
179 imageWidth = image.width,
180 imageHeight = image.height,
181 newHeight = 0,
182 newWidth = 0,
183 topOffset = 0,
184 leftOffset = 0,
185 canvas = null,
186 ctx = null;
187
188 if ( imageWidth < imageHeight ) {
189 // Portrait.
190 newWidth = maxWidth;
191 newHeight = newWidth * imageHeight / imageWidth;
192 topOffset = ( maxHeight - newHeight ) / 2;
193 } else {
194 // Landscape.
195 newHeight = maxHeight;
196 newWidth = newHeight * imageWidth / imageHeight;
197 leftOffset = ( maxWidth - newWidth ) / 2;
198 }
199
200 canvas = d.createElement( 'canvas' );
201
202 canvas.width = maxWidth;
203 canvas.height = maxHeight;
204
205 ctx = canvas.getContext( '2d' );
206 ctx.drawImage( this, leftOffset, topOffset, newWidth, newHeight );
207
208 try {
209 data.thumbnail = canvas.toDataURL( 'image/jpeg' );
210 } catch ( e ) {
211 data.thumbnail = _this.defaultThumb;
212 }
213
214 canvas = null;
215 ctx = null;
216 image = null;
217
218 _this.currentItems.push( data );
219 _this._before( data );
220 _this.send( data );
221 };
222
223 image.src = fileURL;
224
225 return this;
226 };
227
228 /**
229 * Do the ajax request.
230 *
231 * @param {object} data {
232 * The data:
233 *
234 * @type {int} mediaID The media ID.
235 * @type {string} filename The file name.
236 * @type {string} thumbnail The file thumbnail URL.
237 * }
238 * @return this
239 */
240 w.imagify.Optimizer.prototype.send = function( data ) {
241 var _this = this,
242 defaultResponse = {
243 success: false,
244 mediaID: data.mediaID,
245 groupID: this.groupID,
246 context: this.context,
247 filename: data.filename,
248 thumbnail: data.thumbnail,
249 status: 'error',
250 error: ''
251 };
252
253 $.post( {
254 url: this.ajaxUrl,
255 data: {
256 media_id: data.mediaID,
257 context: this.context,
258 optimization_level: this.level
259 },
260 dataType: 'json'
261 } )
262 .done( function( response ) {
263 if ( response.success ) {
264 return;
265 }
266
267 defaultResponse.error = response.data.error;
268
269 _this.processed( defaultResponse );
270 } )
271 .fail( function( jqXHR ) {
272 if ( 200 === jqXHR.status ) {
273 defaultResponse.error = jqXHR.responseText.replace( /<h1>.*<\/h1>\n*/, '' );
274 } else {
275 defaultResponse.error = jqXHR.statusText;
276 }
277
278 _this.processed( defaultResponse );
279 } );
280
281 return this;
282 };
283
284 /**
285 * Callback triggered when an optimization is complete.
286 *
287 * @param {object} e jQuery's Event object.
288 * @param {object} item {
289 * The response:
290 *
291 * @type {int} mediaID The media ID.
292 * @type {string} context The context.
293 * }
294 */
295 w.imagify.Optimizer.prototype.processedCallback = function( e, item ) {
296 var _this = e.data._this;
297
298 if ( item.context !== _this.context ) {
299 return;
300 }
301
302 if ( ! item.mediaID || typeof _this.files[ '_' + item.mediaID ] === 'undefined' ) {
303 return;
304 }
305
306 item.groupID = _this.groupID;
307
308 if ( ! _this.currentItems.length ) {
309 // Trouble.
310 _this.processed( item );
311 return;
312 }
313
314 $.each( _this.currentItems, function( i, mediaData ) {
315 if ( item.mediaID === mediaData.mediaID ) {
316 item.filename = mediaData.filename;
317 item.thumbnail = mediaData.thumbnail;
318 return false;
319 }
320 } );
321
322 _this.processed( item );
323 };
324
325 /**
326 * After a media has been processed.
327 *
328 * @param {object} response {
329 * The response:
330 *
331 * @type {bool} success Whether the optimization succeeded or not ("already optimized" is a success).
332 * @type {int} mediaID The media ID.
333 * @type {string} groupID The group ID.
334 * @type {string} context The context.
335 * @type {string} filename The file name.
336 * @type {string} thumbnail The file thumbnail URL.
337 * @type {string} status The status, like 'optimized', 'already-optimized', 'over-quota', 'error'.
338 * @type {string} error The error message.
339 * }
340 * @return this
341 */
342 w.imagify.Optimizer.prototype.processed = function( response ) {
343 var currentItems = this.currentItems;
344
345 if ( currentItems.length ) {
346 // Remove this media from the "current" list.
347 $.each( currentItems, function( i, mediaData ) {
348 if ( response.mediaID === mediaData.mediaID ) {
349 currentItems.splice( i, 1 );
350 return false;
351 }
352 } );
353
354 this.currentItems = currentItems;
355 }
356
357 // Update stats.
358 if ( response.success && 'already-optimized' !== response.status ) {
359 this.globalOriginalSize += response.originalOverallSize;
360 this.globalOptimizedSize += response.newOverallSize;
361 this.globalGain += response.overallSaving;
362 this.globalPercent = ( 100 - this.globalOptimizedSize / this.globalOptimizedSize * 100 ).toFixed( 2 );
363 }
364
365 ++this.processedMedia;
366 response.progress = Math.floor( this.processedMedia / this.totalMedia * 100 );
367
368 this._each( response );
369
370 if ( this.prefixedMediaIDs.length ) {
371 this.processNext();
372 } else if ( this.totalMedia === this.processedMedia ) {
373 this._done( {
374 globalOriginalSize: this.globalOriginalSize,
375 globalOptimizedSize: this.globalOptimizedSize,
376 globalGain: this.globalGain
377 } );
378 }
379
380 return this;
381 };
382
383 /**
384 * Stop the process.
385 *
386 * @return this
387 */
388 w.imagify.Optimizer.prototype.stopProcess = function() {
389 this.files = {};
390 this.prefixedMediaIDs = [];
391 this.currentItems = [];
392
393 if ( this.doneEvent ) {
394 $( w ).off( this.doneEvent, this.processedCallback );
395 }
396
397 return this;
398 };
399
400 } )(jQuery, document, window);
401