src/assets/js/jquery.images-compare.js   F
last analyzed

Complexity

Total Complexity 83
Complexity/F 1.69

Size

Lines of Code 470
Function Count 49

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
cc 0
c 7
b 0
f 0
nc 48
dl 0
loc 470
rs 3.12
wmc 83
mnd 3
bc 76
fnc 49
bpm 1.551
cpm 1.6938
noi 0

4 Functions

Rating   Name   Duplication   Size   Complexity  
A jquery.images-compare.js ➔ stringRepeat 0 8 2
A $.fn.imagesCompare 0 8 1
A jquery.images-compare.js ➔ ??? 0 3 1
B jquery.images-compare.js ➔ ImagesCompare 0 396 1

How to fix   Complexity   

Complexity

Complex classes like src/assets/js/jquery.images-compare.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/** global: Hammer */
2
;(function ($, window) {
3
    "use strict";
4
5
    function stringRepeat(s, precision) {
6
        // String repeat polyfill
7
        if (!String.prototype.repeat) {
8
            precision = precision || 1;
9
            return new Array(precision + 1).join(s);
10
        }
11
        return s.repeat(precision);
12
    }
13
14
    var pluginName = 'imagesCompare',
15
        defaults = {
16
            initVisibleRatio: 0.5,
17
            interactionMode: "drag", // "drag", "mousemove", "click"
18
            animationDuration: 400, // default animation duration in ms
19
            animationEasing: "swing",
20
            addSeparator: true, // add a html element on the separation
21
            addDragHandle: true, // add a html drag handle element on the separation
22
            precision: 4
23
        };
24
25
    // Our object, using revealing module pattern
26
    function ImagesCompare(element, options) {
27
        element = $(element);
28
        options = $.extend({}, defaults, options);
29
        options.roundFactor = parseInt('1' + stringRepeat('0', options.precision));
30
31
        this._name = pluginName;
32
33
        var frontElement, backElement, separator, dragHandle, lastRatio = 1, size = {
34
            width: 0,
35
            height: 0,
36
            maxWidth: 0,
37
            maxHeight: 0
38
        }, events = {
39
            initialised: "imagesCompare:initialised",
40
            changed: "imagesCompare:changed",
41
            resized: "imagesCompare:resized"
42
        };
43
44
        function onImagesLoaded() {
45
            var images = element.find('img'),
46
                totalImagesCount= images.length,
47
                elementsLoaded = 0;
48
49
            function onImageLoaded(){
50
                if (elementsLoaded >= totalImagesCount) {
51
                    init();
52
                }
53
            }
54
55
            images.each(function() {
56
                // Image already loaded (cached)
57
                if ($(this)[0].complete) {
58
                    totalImagesCount--;
59
                    onImageLoaded();
60
                } else {
61
                    // Image loading / error
62
                    $(this).on('load', function() {
63
                        elementsLoaded++;
64
                        onImageLoaded();
65
                    });
66
                    $(this).on('error', function() {
67
                        elementsLoaded++;
68
                        onImageLoaded();
69
                    });
70
                }
71
            });
72
        }
73
74
        onImagesLoaded();
75
76
        function init() {
77
            updateDom();
78
            patchSize();
79
            initInteractions();
80
81
            $(frontElement).attr('ratio', options.initVisibleRatio);
82
            setVisibleRatio(options.initVisibleRatio);
83
84
            // Let the world know we have done the init
85
            element.trigger({
86
                type: events.initialised
87
            });
88
        }
89
90
        function addResize() {
91
            $(window).on('resize', function (event) {
92
                frontElement.css('clip', '');
93
                patchSize();
94
                setVisibleRatio(lastRatio);
95
96
                // Let the world know we have done some resize updates
97
                element.trigger({
98
                    type: events.resized,
99
                    originalEvent: event
100
                });
101
            });
102
        }
103
104
        function initInteractions() {
105
            options.interactionMode = options.interactionMode.toLowerCase();
106
107
            if (options.interactionMode != "drag" && options.interactionMode != "mousemove" && options.interactionMode != "click") {
108
                console.warn('No valid interactionMode found, valid values are "drag", "mousemove", "click"');
109
            }
110
111
            switch (options.interactionMode) {
112
                case "drag":
113
                    initDrag();
114
                    break;
115
                case "mousemove":
116
                    initMouseMove();
117
                    break;
118
                case "click":
119
                    initClick();
120
                    break;
121
                default:
122
                    initDrag();
123
            }
124
        }
125
126
        function initDrag() {
127
            if (typeof Hammer == 'undefined') {
128
                console.error('Please include the hammerjs library for drag support');
129
            }
130
            addDrag();
131
            addResize();
132
        }
133
134
        function initMouseMove() {
135
            addMouseMove();
136
            addResize();
137
        }
138
139
        function initClick() {
140
            addClick();
141
            addResize();
142
        }
143
144
        function addClick() {
145
            element.on('click', function (event) {
146
                var ratio = getElementRatio(event.pageX);
147
                setVisibleRatio(ratio);
148
            });
149
        }
150
151
        function addMouseMove() {
152
            var lastMove = 0;
153
            var eventThrottle = 1;
154
            element.on('mousemove', function (event) {
155
                event.preventDefault();
156
                var now = Date.now();
157
                if (now > lastMove + eventThrottle) {
158
                    lastMove = now;
159
                    var ratio = getElementRatio(event.pageX);
160
                    setVisibleRatio(ratio);
161
                }
162
            });
163
164
            element.on('mouseout', function (event) {
165
                var ratio = getElementRatio(event.pageX);
166
                setVisibleRatio(ratio);
167
            });
168
        }
169
170
        function addDrag() {
171
            var hammertime = new Hammer(element[0]);
172
            hammertime.get('pan').set({direction: Hammer.DIRECTION_HORIZONTAL});
173
            hammertime.on('pan', function (event) {
174
                var ratio = getElementRatio(event.srcEvent.pageX);
175
                setVisibleRatio(ratio);
176
            });
177
        }
178
179
        function updateDom() {
180
            element.addClass('images-compare-container');
181
            element.css('display', 'inline-block');
182
183
            frontElement = element.find('> *:nth-child(1)');
184
            backElement = element.find('> *:nth-child(2)');
185
186
            frontElement.addClass("images-compare-before");
187
            frontElement.css('display', 'block');
188
            backElement.addClass("images-compare-after");
189
            backElement.css('display', 'block');
190
191
            if (options.addDragHandle) {
192
                buildDragHandle();
193
            }
194
195
            if (options.addSeparator) {
196
                buildSeparator();
197
            }
198
        }
199
200
        function buildSeparator() {
201
            element.prepend("<div class='images-compare-separator'></div>");
202
            separator = element.find(".images-compare-separator");
203
204
        }
205
206
        function buildDragHandle() {
207
            element.prepend("<div class='images-compare-handle'></div>");
208
            dragHandle = element.find(".images-compare-handle");
209
            dragHandle.append("<span class='images-compare-left-arrow'></span>");
210
            dragHandle.append("<span class='images-compare-right-arrow'></span>");
211
        }
212
213
        function patchSize() {
214
            var imgRef = backElement.find('img').first();
215
            setSize(imgRef.width(), imgRef.height(), imgRef.naturalWidth(), imgRef.naturalHeight());
216
            element.css('max-width', size.maxWidth + 'px');
217
            element.css('max-height', size.maxHeight + 'px');
218
            frontElement.width(size.width);
219
            frontElement.height(size.height);
220
        }
221
222
        /**
223
         *
224
         * @param x
225
         * @return float
226
         */
227
        function getElementRatio(x) {
228
            return roundRatio((x - element.offset().left) / frontElement.width());
229
        }
230
231
        /**
232
         *
233
         * @param ratio
234
         * @return float
235
         */
236
        function roundRatio(ratio) {
237
            ratio = Math.round((ratio * options.roundFactor)) / options.roundFactor;
238
            if (ratio > 1) {
239
                ratio = 1;
240
            }
241
242
            if (ratio < 0) {
243
                ratio = 0;
244
            }
245
246
            return ratio;
247
248
        }
249
250
        /**
251
         * Animation request
252
         *
253
         * @param startValue float
254
         * @param endValue float
255
         * @param duration value in ms
256
         * @param easing linear or swing
257
         */
258
        function launchAnimation(startValue, endValue, duration, easing) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
259
            $(frontElement).attr('ratio', startValue).animate({ratio: startValue}, {
260
                duration: 0
261
            });
262
263
            $(frontElement).stop().attr('ratio', startValue).animate({ratio: endValue}, {
264
                duration: duration,
265
                easing: easing,
266
                step: function (now) {
267
                    var width = getRatioValue(now);
268
                    lastRatio = now;
269
                    frontElement.attr('ratio', now).css('clip', 'rect(0, ' + width + 'px, ' + size.height + 'px, 0)');
270
271
                    if (options.addSeparator) {
272
                        separator.css('left', width + 'px');
273
                    }
274
275
                    if (options.addDragHandle) {
276
                        dragHandle.css('left', width + 'px');
277
                    }
278
                },
279
                done: function (animation, jumpedToEnd) {
280
                    var ratio = $(frontElement).attr('ratio');
281
                    // Let the world know something has changed
282
                    element.trigger({
283
                        type: events.changed,
284
                        ratio: ratio,
285
                        value: getRatioValue(ratio),
286
                        animate: true,
287
                        animation : animation,
288
                        jumpedToEnd: jumpedToEnd
289
                    });
290
                }
291
            });
292
        }
293
294
        /**
295
         * Get value to reach, based on a ratio
296
         *
297
         * @param ratio float
298
         * @return {number}
299
         */
300
        function getRatioValue(ratio) {
301
            ratio = Math.round((ratio * options.roundFactor)) / options.roundFactor;
302
            return Math.round(frontElement.width() * ratio);
303
        }
304
305
        /**
306
         * Change visible ratio
307
         *
308
         * @param ratio float
309
         * @param animate boolean Do we want an animation ?
310
         * @param duration in ms
311
         * @param easing 'swing', 'linear'
312
         */
313
        function setVisibleRatio(ratio, animate, duration, easing) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
314
            if (typeof animate == 'undefined') {
315
                animate = false;
316
            }
317
318
            var width = getRatioValue(ratio);
319
320
            if (animate) {
321
                var finalDuration = duration ? duration : options.animationDuration;
322
                var finalEasing = easing ? easing : options.animationEasing;
323
324
                launchAnimation(lastRatio, ratio, finalDuration, finalEasing);
325
326
                // Let the world know something has changed
327
                if (lastRatio != ratio) {
328
                    element.trigger({
329
                        type: events.changed,
330
                        ratio: lastRatio,
331
                        value: width,
332
                        animate: animate
333
                    });
334
                }
335
336
                return;
337
338
            } else {
339
                frontElement.stop().css('clip', 'rect(0, ' + width + 'px, ' + size.height + 'px, 0)');
340
341
                if (options.addSeparator) {
342
                    $(separator).stop().css('left', width + 'px');
343
                }
344
345
                if (options.addDragHandle) {
346
                    dragHandle.css('left', width + 'px');
347
                }
348
            }
349
350
            // Let the world know something has changed
351
            if (lastRatio != ratio) {
352
                element.trigger({
353
                    type: events.changed,
354
                    ratio: ratio,
355
                    value: width,
356
                    animate: animate
357
                });
358
            }
359
360
            lastRatio = ratio;
361
        }
362
363
        function setSize(width, height, maxWidth, maxHeight) {
364
            if (typeof width != 'undefined') {
365
                setWidth(width);
366
            }
367
            if (typeof height != 'undefined') {
368
                setHeight(height);
369
            }
370
            if (typeof maxWidth != 'undefined') {
371
                setMaxWidth(maxWidth);
372
            }
373
            if (typeof maxHeight != 'undefined') {
374
                setMaxHeight(maxHeight);
375
            }
376
            return size;
377
        }
378
379
        function setWidth(width) {
380
            size.width = width;
381
            return size;
382
        }
383
384
        function setMaxWidth(maxWidth) {
385
            size.maxWidth = maxWidth;
386
            return size;
387
        }
388
389
        function setHeight(height) {
390
            size.height = height;
391
            return size;
392
        }
393
394
        function setMaxHeight(maxHeight) {
395
            size.maxHeight = maxHeight;
396
            return size;
397
        }
398
399
        // public function declaration
400
        // returning element to preserve chaining
401
        return {
402
            "setValue": function (ratio, animate, duration, easing) {
403
                setVisibleRatio(ratio, animate, duration, easing);
404
                return element;
405
            },
406
            "getValue": function () {
407
                return lastRatio;
408
            },
409
            "on": function (eventName, callback) {
410
                element.on(eventName, callback);
411
                return element;
412
            },
413
            "off": function (eventName, callback) {
414
                element.off(eventName, callback);
415
                return element;
416
            },
417
            "events": function () {
418
                return events;
419
            }
420
        };
421
    }
422
423
424
    /**
425
     * Plugin declaration
426
     *
427
     * @param userOptions
428
     * @return {*}
429
     */
430
    $.fn.imagesCompare = function (userOptions) {
431
        var options = $.extend(defaults, userOptions);
432
        return this.each(function () {
433
            if (!$.data(this, pluginName)) {
434
                $.data(this, pluginName, new ImagesCompare(this, options));
435
            }
436
        });
437
    };
438
439
})(jQuery, window, document);
440
441
// http://www.jacklmoore.com/notes/naturalwidth-and-naturalheight-in-ie/
442
(function ($) {
443
    var props = ['Width', 'Height'], prop, propsLength;
444
445
    propsLength = props.length;
446
447
    for (var index = 0; index < propsLength; index++) {
448
        prop = props[index];
449
        /*jslint loopfunc: true */
450
        (function (natural, prop) {
451
            $.fn[natural] = (natural in document.createElement('img')) ?
452
                function () {
453
                    return this[0][natural];
454
                } :
455
                function () {
456
                    var
457
                        node = this[0],
458
                        img,
459
                        value = 0;
460
461
                    if (node.tagName.toLowerCase() === 'img') {
462
                        img = document.createElement('img');
463
                        img.src = node.src;
464
                        value = img[prop];
465
                    }
466
                    return value;
467
                };
468
        }('natural' + prop, prop.toLowerCase()));
469
        /*jslint loopfunc: false */
470
    }
471
}(jQuery));
472