compare-images-slider 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  A simple slider for comparing two images visually.
4
4
 
5
+ <img style="max-width: 100%" src="https://i.imgur.com/e9m4QaU.jpeg" alt="Compare Images Slider Screenshot">
6
+
7
+ ## Features
8
+
9
+ - Lightweight
10
+ - Minimal DOM depth
11
+ - No dependencies
12
+ - Mobile friendly
13
+ - Vertical slider
14
+ - Inertia physics
15
+ - Bounce back
16
+ - Customizable via CSS
17
+
18
+ ## Demo
19
+
20
+ [Check out the demo](https://stamat.github.io/compare-images-slider/)
21
+
22
+
5
23
  ## Installation
6
24
 
7
25
  ```bash
@@ -51,15 +69,41 @@ document.addEventListener('CompareImagesSliderLoaded', function() {
51
69
 
52
70
  ## Options
53
71
 
54
- Coming soon...
72
+ ```javascript
73
+ // Default options
74
+ const options = {
75
+ inertia: false, // inertia physics, you can flick the handle
76
+ friction: 0.9, // the friction of the inertia
77
+ bounce: false, // will bounce back when intertia is enabled and the boundary is reached
78
+ bounceFactor: 0.1, // the force of the bounce
79
+ vertical: false, // vertical slider
80
+ onlyHandle: true // only the handle is draggable
81
+ }
82
+
83
+ new CompareImagesSlider(slider, options);
84
+ ```
85
+
86
+ Available attribute options:
87
+
88
+ - `vertical` - vertical slider or `data-vertical`
89
+
90
+ ```html
91
+ <div class="js-compare-images-slider compare-images-slider" vertical>
92
+ <img src="img.jpg" alt="">
93
+ <div class="frame">
94
+ <img src="img-alt.jpg" alt="">
95
+ </div>
96
+ <span class="handle"></span>
97
+ </div>
98
+ ```
55
99
 
56
100
  ## TODO:
57
101
 
58
- - [ ] Add options
59
- - [ ] Scroll block on drag
60
- - [ ] Vertical option
61
- - [ ] Add factory class, migrate the general factory class to the book of spells prior to that
62
- - [ ] Refactor onDrag and move it to the book of spells
102
+ - [x] Add options
103
+ - [x] Scroll block on drag
104
+ - [x] Vertical option
105
+ - [ ] Add factory class, migrate the general factory class to the book of spells prior to that? Better turn this into custom element!
106
+ - [x] Refactor onDrag and move it to the book of spells
63
107
  - [ ] Add initialized state, don't initialize twice
64
108
 
65
109
  ---
@@ -1,4 +1,4 @@
1
- /* compare-images-slider v1.0.0 | https://stamat.github.io/compare-images-slider/ | MIT License */
1
+ /* compare-images-slider v1.0.2 | https://stamat.github.io/compare-images-slider/ | MIT License */
2
2
  @charset "UTF-8";
3
3
  :root {
4
4
  --compare-images-slider-initial-position: 50%;
@@ -17,6 +17,7 @@
17
17
  .compare-images-slider > img {
18
18
  max-width: 100%;
19
19
  height: auto;
20
+ display: block;
20
21
  }
21
22
  .compare-images-slider .frame {
22
23
  position: absolute;
@@ -29,6 +30,7 @@
29
30
  }
30
31
  .compare-images-slider .frame > img {
31
32
  height: auto;
33
+ display: block;
32
34
  }
33
35
  .compare-images-slider .handle {
34
36
  position: absolute;
@@ -44,6 +46,7 @@
44
46
  .compare-images-slider .handle:after {
45
47
  position: absolute;
46
48
  top: 50%;
49
+ left: 50%;
47
50
  width: 42px;
48
51
  height: 42px;
49
52
  transform: translate3d(-50%, -50%, 0);
@@ -54,4 +57,20 @@
54
57
  text-align: center;
55
58
  border-radius: 21px;
56
59
  }
60
+ .compare-images-slider[data-vertical] .frame, .compare-images-slider[vertical] .frame {
61
+ width: 100%;
62
+ height: var(--compare-images-slider-initial-position);
63
+ }
64
+ .compare-images-slider[data-vertical] .handle, .compare-images-slider[vertical] .handle {
65
+ left: 0;
66
+ top: var(--compare-images-slider-initial-position);
67
+ width: 100%;
68
+ height: 2px;
69
+ margin-top: -1px;
70
+ margin-left: 0;
71
+ cursor: ns-resize;
72
+ }
73
+ .compare-images-slider[data-vertical] .handle:after, .compare-images-slider[vertical] .handle:after {
74
+ content: "↕";
75
+ }
57
76
  /*# sourceMappingURL=compare-images-slider.css.map */
@@ -1 +1 @@
1
- {"version":3,"sourceRoot":"","sources":["file:///Users/stamat/Sites/localhost/compare-images-slider/src/styles/index.scss"],"names":[],"mappings":";;AAAA;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA","sourcesContent":[":root {\n --compare-images-slider-initial-position: 50%;\n}\n\n.compare-images-slider {\n display: block;\n position: relative;\n user-select: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n\n img {\n pointer-events: none;\n }\n\n & > img {\n max-width: 100%;\n height: auto;\n }\n\n .frame {\n position: absolute;\n top:0;\n left: 0;\n height: 100%;\n width: var(--compare-images-slider-initial-position);\n overflow: hidden;\n z-index: 2;\n\n & > img {\n height: auto;\n }\n }\n\n .handle {\n position:absolute;\n left: var(--compare-images-slider-initial-position);\n top:0;\n bottom:0;\n width: 2px;\n margin-left: -1px;\n height: 100%;\n cursor: ew-resize;\n z-index: 3;\n }\n\n .handle:after {\n position: absolute;\n top: 50%;\n width: 42px;\n height: 42px;\n transform: translate3d(-50%, -50%, 0);\n\n content:'↔';\n line-height: 38px;\n font-size: 28px;\n background: #fff;\n text-align: center;\n border-radius: 21px;\n }\n}\n"]}
1
+ {"version":3,"sourceRoot":"","sources":["file:///Users/stamat/Sites/localhost/compare-images-slider/src/styles/index.scss"],"names":[],"mappings":";;AAAA;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;;AAKA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE","sourcesContent":[":root {\n --compare-images-slider-initial-position: 50%;\n}\n\n.compare-images-slider {\n display: block;\n position: relative;\n user-select: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n\n img {\n pointer-events: none;\n }\n\n & > img {\n max-width: 100%;\n height: auto;\n display: block;\n }\n\n .frame {\n position: absolute;\n top:0;\n left: 0;\n height: 100%;\n width: var(--compare-images-slider-initial-position);\n overflow: hidden;\n z-index: 2;\n\n & > img {\n height: auto;\n display: block;\n }\n }\n\n .handle {\n position:absolute;\n left: var(--compare-images-slider-initial-position);\n top:0;\n bottom:0;\n width: 2px;\n margin-left: -1px;\n height: 100%;\n cursor: ew-resize;\n z-index: 3;\n }\n\n .handle:after {\n position: absolute;\n top: 50%;\n left: 50%;\n width: 42px;\n height: 42px;\n transform: translate3d(-50%, -50%, 0);\n\n content:'↔';\n line-height: 38px;\n font-size: 28px;\n background: #fff;\n text-align: center;\n border-radius: 21px;\n }\n\n &[data-vertical],\n &[vertical] {\n .frame {\n width: 100%;\n height: var(--compare-images-slider-initial-position);\n }\n\n .handle {\n left: 0;\n top: var(--compare-images-slider-initial-position);\n width: 100%;\n height: 2px;\n margin-top: -1px;\n margin-left: 0;\n cursor: ns-resize;\n }\n\n .handle:after {\n content:'↕';\n }\n }\n}\n"]}
@@ -1,81 +1,177 @@
1
- /* compare-images-slider v1.0.0 | https://stamat.github.io/compare-images-slider/ | MIT License */
1
+ /* compare-images-slider v1.0.2 | https://stamat.github.io/compare-images-slider/ | MIT License */
2
2
  (() => {
3
3
  // node_modules/book-of-spells/src/helpers.mjs
4
+ function shallowMerge(target, source) {
5
+ for (const key in source) {
6
+ target[key] = source[key];
7
+ }
8
+ return target;
9
+ }
10
+ function isObject(o) {
11
+ return typeof o === "object" && !Array.isArray(o) && o !== null;
12
+ }
13
+ function isFunction(o) {
14
+ return typeof o === "function";
15
+ }
4
16
  function percentage(num, total) {
5
17
  if (!num || !total || Number.isNaN(num) || Number.isNaN(total))
6
18
  return 0;
7
19
  return num / total * 100;
8
20
  }
9
21
 
10
- // src/scripts/compare-images-slider.js
11
- function onDrag(element, callback) {
12
- let startX = 0;
13
- let startY = 0;
14
- let endX = 0;
15
- let endY = 0;
22
+ // node_modules/book-of-spells/src/dom.mjs
23
+ function drag(element, opts) {
24
+ if (!element || !(element instanceof Element))
25
+ return;
26
+ if (element.getAttribute("drag-enabled") === "true")
27
+ return;
28
+ let x = 0;
29
+ let y = 0;
30
+ let prevX = 0;
31
+ let prevY = 0;
32
+ let velocityX = 0;
33
+ let velocityY = 0;
16
34
  let dragging = false;
17
- let rect = element.getBoundingClientRect();
35
+ let rect = null;
36
+ let inertiaId = null;
37
+ const options = {
38
+ inertia: false,
39
+ bounce: false,
40
+ friction: 0.9,
41
+ bounceFactor: 0.2,
42
+ callback: null,
43
+ preventDefaultTouch: true
44
+ };
45
+ if (isFunction(opts)) {
46
+ options.callback = opts;
47
+ } else if (isObject(opts)) {
48
+ shallowMerge(options, opts);
49
+ }
50
+ options.friction = Math.abs(options.friction);
51
+ options.bounceFactor = Math.abs(options.bounceFactor);
52
+ element.setAttribute("drag-enabled", "true");
53
+ element.setAttribute("dragging", "false");
54
+ const calcPageRelativeRect = function() {
55
+ const origRect = element.getBoundingClientRect();
56
+ const rect2 = {
57
+ top: origRect.top + window.scrollY,
58
+ left: origRect.left + window.scrollX,
59
+ width: origRect.width,
60
+ height: origRect.height
61
+ };
62
+ return rect2;
63
+ };
64
+ rect = calcPageRelativeRect();
18
65
  const handleStart = function(e) {
19
- const carrier = e.type === "touchstart" ? e.touches[0] : e;
20
- startX = carrier.clientX;
21
- startY = carrier.clientY;
66
+ setXY(e);
22
67
  dragging = true;
23
- rect = element.getBoundingClientRect();
24
- const xPercentage = percentage(startX - rect.left, rect.width);
25
- const yPercentage = percentage(startY - rect.top, rect.height);
26
- const event = new CustomEvent("dragstart", { detail: { target: element, startX, startY, rect, xPercentage, yPercentage } });
68
+ rect = calcPageRelativeRect();
69
+ element.setAttribute("dragging", "true");
70
+ if (inertiaId) {
71
+ cancelAnimationFrame(inertiaId);
72
+ inertiaId = null;
73
+ }
74
+ const event = new CustomEvent("dragstart", { detail: getDetail() });
27
75
  element.dispatchEvent(event);
28
76
  };
29
77
  const handleMove = function(e) {
30
78
  if (!dragging)
31
79
  return;
32
- const carrier = e.type === "touchmove" ? e.touches[0] : e;
33
- endX = carrier.clientX;
34
- endY = carrier.clientY;
35
- handleDragGesture();
80
+ setXY(e);
81
+ velocityX = x - prevX;
82
+ velocityY = y - prevY;
83
+ const detail = getDetail();
84
+ if (options.callback)
85
+ options.callback(detail);
86
+ const event = new CustomEvent("drag", { detail });
87
+ element.dispatchEvent(event);
36
88
  };
37
89
  const handleEnd = function() {
38
90
  dragging = false;
39
- const event = new CustomEvent("dragend", { detail: { target: element, startX, startY, rect, endX, endY } });
91
+ element.setAttribute("dragging", "false");
92
+ if (options.inertia)
93
+ inertiaId = requestAnimationFrame(inertia);
94
+ const event = new CustomEvent("dragend", { detail: getDetail() });
40
95
  element.dispatchEvent(event);
41
96
  };
42
- const handleDragGesture = function() {
43
- const deltaX = endX - startX;
44
- const deltaY = endY - startY;
45
- const left = deltaX < 0;
46
- const up = deltaY < 0;
47
- const xPercentage = percentage(endX - rect.left, rect.width);
48
- const yPercentage = percentage(endY - rect.top, rect.height);
97
+ const setXY = function(e) {
98
+ const carrier = e.touches ? e.touches[0] : e;
99
+ if (e.touches && options.preventDefaultTouch)
100
+ e.preventDefault();
101
+ prevX = x;
102
+ prevY = y;
103
+ x = carrier.pageX;
104
+ y = carrier.pageY;
105
+ };
106
+ const getDetail = function() {
107
+ const relativeX = x - rect.left;
108
+ const relativeY = y - rect.top;
109
+ const xPercentage = percentage(relativeX, rect.width);
110
+ const yPercentage = percentage(relativeY, rect.height);
49
111
  const detail = {
50
112
  target: element,
51
- deltaX,
52
- deltaY,
53
- startX,
54
- startY,
55
- endX,
56
- endY,
57
- horizontalDirection: left ? "left" : "right",
58
- verticalDirection: up ? "up" : "down",
113
+ x,
114
+ y,
115
+ relativeX,
116
+ relativeY,
59
117
  xPercentage,
60
- yPercentage
118
+ yPercentage,
119
+ velocityX,
120
+ velocityY,
121
+ prevX,
122
+ prevY
61
123
  };
62
- if (xPercentage < 0) {
124
+ if (xPercentage < 0)
63
125
  detail.xPercentage = 0;
64
- }
65
- if (xPercentage > 100) {
126
+ if (xPercentage > 100)
66
127
  detail.xPercentage = 100;
67
- }
68
- if (yPercentage < 0) {
128
+ if (yPercentage < 0)
69
129
  detail.yPercentage = 0;
70
- }
71
- if (yPercentage > 100) {
130
+ if (yPercentage > 100)
72
131
  detail.yPercentage = 100;
132
+ return detail;
133
+ };
134
+ const inertia = function() {
135
+ x += velocityX;
136
+ y += velocityY;
137
+ velocityX *= options.friction;
138
+ velocityY *= options.friction;
139
+ if (options.bounce) {
140
+ if (x < rect.left) {
141
+ x = rect.left;
142
+ velocityX *= -options.bounceFactor;
143
+ }
144
+ if (x > rect.width + rect.left) {
145
+ x = rect.width + rect.left;
146
+ velocityX *= -options.bounceFactor;
147
+ }
148
+ if (y < rect.top) {
149
+ y = rect.top;
150
+ velocityY *= -options.bounceFactor;
151
+ }
152
+ if (y > rect.height + rect.top) {
153
+ y = rect.height + rect.top;
154
+ velocityY *= -options.bounceFactor;
155
+ }
73
156
  }
74
- if (callback) {
75
- callback(detail);
157
+ if (Math.abs(velocityX) < 0.1)
158
+ velocityX = 0;
159
+ if (Math.abs(velocityY) < 0.1)
160
+ velocityY = 0;
161
+ const detail = getDetail();
162
+ if (velocityX !== 0 || velocityY !== 0) {
163
+ if (options.callback)
164
+ options.callback(detail);
165
+ const event = new CustomEvent("draginertia", { detail });
166
+ element.dispatchEvent(event);
167
+ inertiaId = requestAnimationFrame(inertia);
168
+ } else {
169
+ inertiaId = null;
170
+ if (options.callback)
171
+ options.callback(detail);
172
+ const event = new CustomEvent("draginertiaend", { detail });
173
+ element.dispatchEvent(event);
76
174
  }
77
- const event = new CustomEvent("drag", { detail });
78
- element.dispatchEvent(event);
79
175
  };
80
176
  element.addEventListener("mousedown", handleStart);
81
177
  element.addEventListener("mousemove", handleMove);
@@ -91,35 +187,100 @@
91
187
  element.removeEventListener("touchstart", handleStart);
92
188
  element.removeEventListener("touchmove", handleMove);
93
189
  element.removeEventListener("touchend", handleEnd);
190
+ if (inertiaId) {
191
+ cancelAnimationFrame(inertiaId);
192
+ inertiaId = null;
193
+ }
94
194
  }
95
195
  };
96
196
  }
197
+
198
+ // src/scripts/compare-images-slider.js
97
199
  var CompareImagesSlider = class {
98
200
  constructor(element, options) {
99
201
  this.element = element;
100
202
  this.frame = this.element.querySelector(".frame");
101
203
  this.second = this.frame.querySelector(":scope > img");
102
204
  this.handle = this.element.querySelector(".handle");
205
+ this.options = {
206
+ inertia: false,
207
+ bounce: false,
208
+ friction: 0.9,
209
+ bounceFactor: 0.1,
210
+ onlyHandle: true,
211
+ vertical: false
212
+ };
213
+ if (options)
214
+ shallowMerge(this.options, options);
215
+ this.checkAndApplyAttribute("vertical");
216
+ if (this.options.vertical && !(this.element.dataset.vertical || this.element.hasAttribute("vertical")))
217
+ this.element.setAttribute("vertical", "");
218
+ if (this.options.onlyHandle)
219
+ this.options.preventDefaultTouch = false;
103
220
  window.addEventListener("resize", () => {
104
221
  requestAnimationFrame(this.setupSecondImage.bind(this));
105
222
  });
106
223
  this.setupSecondImage();
107
- this.drag = onDrag(this.element);
108
- this.element.addEventListener("dragstart", this.updateVisibleHandler.bind(this));
109
- this.element.addEventListener("drag", this.updateVisibleHandler.bind(this));
224
+ this.drag = drag(this.element, this.options);
225
+ this.handleDragBound = false;
226
+ this.boundUpdateVisibleHandler = this.updateVisibleHandler.bind(this);
227
+ const preventDefault = (e) => {
228
+ if (this.handleDragBound)
229
+ e.preventDefault();
230
+ };
231
+ const addEventListeners = () => {
232
+ if (this.handleDragBound)
233
+ return;
234
+ this.handleDragBound = true;
235
+ this.element.addEventListener("dragstart", this.boundUpdateVisibleHandler);
236
+ this.element.addEventListener("drag", this.boundUpdateVisibleHandler);
237
+ this.element.addEventListener("draginertia", this.boundUpdateVisibleHandler);
238
+ this.element.addEventListener("draginertiaend", () => {
239
+ this.element.removeEventListener("draginertia", this.boundUpdateVisibleHandler);
240
+ });
241
+ this.element.addEventListener("touchstart", preventDefault);
242
+ };
243
+ const removeEventListeners = () => {
244
+ if (!this.handleDragBound)
245
+ return;
246
+ this.handleDragBound = false;
247
+ this.element.removeEventListener("dragstart", this.boundUpdateVisibleHandler);
248
+ this.element.removeEventListener("drag", this.boundUpdateVisibleHandler);
249
+ this.element.removeEventListener("touchstart", preventDefault);
250
+ };
251
+ if (this.options.onlyHandle) {
252
+ this.handle.addEventListener("mousedown", addEventListeners);
253
+ this.handle.addEventListener("touchstart", addEventListeners);
254
+ document.addEventListener("mouseup", removeEventListeners);
255
+ document.addEventListener("touchend", removeEventListeners);
256
+ } else {
257
+ this.element.addEventListener("dragstart", this.boundUpdateVisibleHandler);
258
+ this.element.addEventListener("drag", this.boundUpdateVisibleHandler);
259
+ this.element.addEventListener("draginertia", this.boundUpdateVisibleHandler);
260
+ }
261
+ }
262
+ checkAndApplyAttribute(attribute) {
263
+ if (this.element.dataset[attribute] || this.element.hasAttribute(attribute))
264
+ this.options[attribute] = true;
110
265
  }
111
266
  setupSecondImage() {
112
267
  const width = this.element.offsetWidth + "px";
113
268
  this.second.style.width = width;
114
269
  }
115
270
  updateVisibleHandler(e) {
271
+ if (this.options.vertical) {
272
+ this.frame.style.height = e.detail.yPercentage + "%";
273
+ this.handle.style.top = e.detail.yPercentage + "%";
274
+ return;
275
+ }
116
276
  this.frame.style.width = e.detail.xPercentage + "%";
117
277
  this.handle.style.left = e.detail.xPercentage + "%";
118
278
  }
119
279
  destroy() {
120
280
  this.drag.destroy();
121
- this.element.removeEventListener("dragstart", this.updateVisibleHandler.bind(this));
122
- this.element.removeEventListener("drag", this.updateVisibleHandler.bind(this));
281
+ this.element.removeEventListener("dragstart", this.boundUpdateVisibleHandler);
282
+ this.element.removeEventListener("drag", this.boundUpdateVisibleHandler);
283
+ this.element.removeEventListener("draginertia", this.boundUpdateVisibleHandler);
123
284
  }
124
285
  };
125
286