Skip to content

Commit 0ba0d06

Browse files
committed
feat: remove rotateLock functionality in favor of new smooth rotation
1 parent 19db197 commit 0ba0d06

File tree

6 files changed

+102
-98
lines changed

6 files changed

+102
-98
lines changed

src/atropos.d.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ export interface AtroposOptions {
66
activeOffset?: number;
77
shadowOffset?: number;
88
shadowScale?: number;
9-
durationEnter?: number;
10-
durationLeave?: number;
11-
rotateLock?: boolean;
9+
duration?: number;
1210
rotate?: boolean;
1311
rotateTouch?: boolean | 'scroll-x' | 'scroll-y';
1412
rotateXMax?: number;

src/atropos.js

Lines changed: 96 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,6 @@
11
/* eslint-disable no-restricted-globals */
22
const $ = (el, sel) => el.querySelector(sel);
33
const $$ = (el, sel) => el.querySelectorAll(sel);
4-
const $setDuration = (el, duration) => {
5-
el.style.transitionDuration = duration;
6-
};
7-
const $setTransform = (el, transform) => {
8-
el.style.transform = transform;
9-
};
10-
const $setOpacity = (el, opacity) => {
11-
el.style.opacity = opacity;
12-
};
13-
const $on = (el, event, handler, props) => el.addEventListener(event, handler, props);
14-
const $off = (el, event, handler, props) => el.removeEventListener(event, handler, props);
154

165
const removeUndefinedProps = (obj = {}) => {
176
const result = {};
@@ -30,9 +19,7 @@ function Atropos(originalParams = {}) {
3019
activeOffset: 50,
3120
shadowOffset: 50,
3221
shadowScale: 1,
33-
durationEnter: 300,
34-
durationLeave: 600,
35-
rotateLock: true,
22+
duration: 300,
3623
rotate: true,
3724
rotateTouch: true,
3825
rotateXMax: 15,
@@ -55,22 +42,53 @@ function Atropos(originalParams = {}) {
5542
const { params } = self;
5643

5744
let rotateEl;
58-
let rotated;
5945
let scaleEl;
6046
let innerEl;
6147

62-
let enterRotateX;
63-
let enterRotateY;
64-
6548
let elBoundingClientRect;
6649
let eventsElBoundingClientRect;
6750

68-
let rotateXLock = true;
69-
let rotateYLock = true;
70-
7151
let shadowEl;
7252
let highlightEl;
7353

54+
let isScrolling;
55+
let clientXStart;
56+
let clientYStart;
57+
58+
const queue = [];
59+
let queueFrameId;
60+
const purgeQueue = () => {
61+
queueFrameId = requestAnimationFrame(() => {
62+
queue.forEach((data) => {
63+
if (typeof data === 'function') {
64+
data();
65+
} else {
66+
const { element, prop, value } = data;
67+
element.style[prop] = value;
68+
}
69+
});
70+
queue.splice(0, queue.length);
71+
purgeQueue();
72+
});
73+
};
74+
purgeQueue();
75+
76+
const $setDuration = (element, value) => {
77+
queue.push({ element, prop: 'transitionDuration', value });
78+
};
79+
const $setEasing = (element, value) => {
80+
queue.push({ element, prop: 'transitionTimingFunction', value });
81+
};
82+
const $setTransform = (element, value) => {
83+
queue.push({ element, prop: 'transform', value });
84+
};
85+
const $setOpacity = (element, value) => {
86+
queue.push({ element, prop: 'opacity', value });
87+
};
88+
const $on = (element, event, handler, props) => element.addEventListener(event, handler, props);
89+
const $off = (element, event, handler, props) =>
90+
element.removeEventListener(event, handler, props);
91+
7492
const createShadow = () => {
7593
let created;
7694
shadowEl = $(el, '.atropos-shadow');
@@ -107,6 +125,7 @@ function Atropos(originalParams = {}) {
107125
rotateYPercentage = 0,
108126
duration,
109127
opacityOnly,
128+
easeOut,
110129
}) => {
111130
const getOpacity = (element) => {
112131
if (element.dataset.atroposOpacity && typeof element.dataset.atroposOpacity === 'string') {
@@ -116,6 +135,7 @@ function Atropos(originalParams = {}) {
116135
};
117136
$$(el, '[data-atropos-offset], [data-atropos-opacity]').forEach((childEl) => {
118137
$setDuration(childEl, duration);
138+
$setEasing(childEl, easeOut ? 'ease-out' : '');
119139
const elementOpacity = getOpacity(childEl);
120140
if (rotateXPercentage === 0 && rotateYPercentage === 0) {
121141
if (!opacityOnly) $setTransform(childEl, `translate3d(0, 0, 0)`);
@@ -143,30 +163,31 @@ function Atropos(originalParams = {}) {
143163
};
144164

145165
const onPointerEnter = (e) => {
166+
isScrolling = undefined;
146167
if (e.type === 'pointerdown' && e.pointerType === 'mouse') return;
147168
if (e.type === 'pointerenter' && e.pointerType !== 'mouse') return;
148169
if (e.type === 'pointerdown') {
149170
e.preventDefault();
150171
}
151-
el.classList.add('atropos-active');
152-
$setDuration(rotateEl, '0ms');
153-
rotated = false;
154-
enterRotateX = undefined;
155-
enterRotateY = undefined;
156-
rotateXLock = true;
157-
rotateYLock = true;
172+
clientXStart = e.clientX;
173+
clientYStart = e.clientY;
174+
queue.push(() => el.classList.add('atropos-active'));
175+
$setDuration(rotateEl, `${params.duration}ms`);
176+
$setEasing(rotateEl, 'ease-out');
158177
$setTransform(scaleEl, `translate3d(0,0, ${params.activeOffset}px)`);
159-
$setDuration(scaleEl, `${params.rotateLock ? params.durationEnter : 0}ms`);
178+
$setDuration(scaleEl, `${params.duration}ms`);
179+
$setEasing(scaleEl, 'ease-out');
160180
if (shadowEl) {
161-
$setDuration(shadowEl, `${params.rotateLock ? params.durationEnter : 0}ms`);
181+
$setDuration(shadowEl, `${params.duration}ms`);
182+
$setEasing(shadowEl, 'ease-out');
162183
}
163184

164185
self.isActive = true;
165186
if (typeof params.onEnter === 'function') params.onEnter();
166187
};
167188

168189
const onTouchMove = (e) => {
169-
if (rotated && e.cancelable) {
190+
if (isScrolling === false && e.cancelable) {
170191
e.preventDefault();
171192
}
172193
};
@@ -216,51 +237,32 @@ function Atropos(originalParams = {}) {
216237
rotateX = (params.rotateXMax * (coordY - centerY)) / (parentHeight - height / 2);
217238
}
218239

219-
if (params.rotateLock) {
220-
if (typeof enterRotateY === 'undefined') {
221-
enterRotateY = rotateY;
222-
rotateYLock = true;
223-
}
224-
if (typeof enterRotateX === 'undefined') {
225-
enterRotateX = rotateX;
226-
rotateXLock = true;
227-
}
228-
if (rotateYLock) {
229-
if (enterRotateY < 0) {
230-
if (rotateY < 0) rotateY = 0;
231-
if (rotateY > 0) rotateYLock = false;
232-
}
233-
if (enterRotateY > 0) {
234-
if (rotateY > 0) rotateY = 0;
235-
if (rotateY < 0) rotateYLock = false;
236-
}
237-
}
238-
if (rotateXLock) {
239-
if (enterRotateX < 0) {
240-
if (rotateX < 0) rotateX = 0;
241-
if (rotateX > 0) rotateXLock = false;
242-
}
243-
if (enterRotateX > 0) {
244-
if (rotateX > 0) rotateX = 0;
245-
if (rotateX < 0) rotateXLock = false;
246-
}
247-
}
248-
}
249-
250240
rotateX = Math.min(Math.max(-rotateX, -params.rotateXMax), params.rotateXMax);
251241
if (params.rotateXInvert) rotateX = -rotateX;
252242
rotateY = Math.min(Math.max(-rotateY, -params.rotateYMax), params.rotateYMax);
253243
if (params.rotateYInvert) rotateY = -rotateY;
254244

255-
if (typeof params.rotateTouch === 'string' && (rotateX !== 0 || rotateY !== 0)) {
256-
if (!rotated) {
257-
rotated = true;
258-
el.classList.add('atropos-rotate-touch');
245+
if (
246+
typeof params.rotateTouch === 'string' &&
247+
(rotateX !== 0 || rotateY !== 0) &&
248+
typeof isScrolling === 'undefined'
249+
) {
250+
const diffX = clientX - clientXStart;
251+
const diffY = clientY - clientYStart;
252+
if (diffX * diffX + diffY * diffY >= 25) {
253+
const touchAngle = (Math.atan2(Math.abs(diffY), Math.abs(diffX)) * 180) / Math.PI;
254+
isScrolling = params.rotateTouch === 'scroll-y' ? touchAngle > 45 : 90 - touchAngle > 45;
259255
}
260-
if (e.cancelable) {
261-
e.preventDefault();
256+
if (isScrolling === false) {
257+
el.classList.add('atropos-rotate-touch');
258+
if (e.cancelable) {
259+
e.preventDefault();
260+
}
262261
}
263262
}
263+
if (e.pointerType !== 'mouse' && isScrolling) {
264+
return;
265+
}
264266
const rotateXPercentage = (rotateX / params.rotateXMax) * 100;
265267
const rotateYPercentage = (rotateY / params.rotateYMax) * 100;
266268

@@ -277,16 +279,24 @@ function Atropos(originalParams = {}) {
277279
);
278280

279281
if (highlightEl) {
280-
$setDuration(highlightEl, '0ms');
282+
$setDuration(highlightEl, `${params.duration}ms`);
283+
$setEasing(highlightEl, 'ease-out');
281284
$setTransform(
282285
highlightEl,
283286
`translate3d(${-rotateYPercentage * 0.25}%, ${rotateXPercentage * 0.25}%, 0)`,
284287
);
285-
highlightEl.style.opacity =
286-
Math.max(Math.abs(rotateXPercentage), Math.abs(rotateYPercentage)) / 100;
288+
$setOpacity(
289+
highlightEl,
290+
Math.max(Math.abs(rotateXPercentage), Math.abs(rotateYPercentage)) / 100,
291+
);
287292
}
288293

289-
setChildrenOffset({ rotateXPercentage, rotateYPercentage, duration: '0ms' });
294+
setChildrenOffset({
295+
rotateXPercentage,
296+
rotateYPercentage,
297+
duration: `${params.duration}ms`,
298+
easeOut: true,
299+
});
290300

291301
if (typeof params.onRotate === 'function') params.onRotate(rotateX, rotateY);
292302
};
@@ -297,24 +307,29 @@ function Atropos(originalParams = {}) {
297307
if (!self.isActive) return;
298308
if (e && e.type === 'pointerup' && e.pointerType === 'mouse') return;
299309
if (e && e.type === 'pointerleave' && e.pointerType !== 'mouse') return;
300-
if (typeof params.rotateTouch === 'string' && rotated) {
310+
if (typeof params.rotateTouch === 'string' && isScrolling) {
301311
el.classList.remove('atropos-rotate-touch');
302312
}
303-
el.classList.remove('atropos-active');
313+
queue.push(() => el.classList.remove('atropos-active'));
314+
$setDuration(scaleEl, `${params.duration}ms`);
315+
$setEasing(scaleEl, '');
304316
$setTransform(scaleEl, `translate3d(0,0, ${0}px)`);
305-
$setDuration(scaleEl, `${params.durationLeave}ms`);
306317
if (shadowEl) {
307-
$setDuration(shadowEl, `${params.durationLeave}ms`);
318+
$setDuration(shadowEl, `${params.duration}ms`);
319+
$setEasing(shadowEl, '');
308320
}
309321
if (highlightEl) {
310-
$setDuration(highlightEl, `${params.durationLeave}ms`);
322+
$setDuration(highlightEl, `${params.duration}ms`);
323+
$setEasing(highlightEl, '');
311324
$setTransform(highlightEl, `translate3d(0, 0, 0)`);
312-
highlightEl.style.opacity = 0;
325+
$setOpacity(highlightEl, 0);
313326
}
314-
$setDuration(rotateEl, `${params.durationLeave}ms`);
327+
$setDuration(rotateEl, `${params.duration}ms`);
328+
$setEasing(rotateEl, '');
315329
$setTransform(rotateEl, `translate3d(0,0,0) rotateX(0deg) rotateY(0deg)`);
316330

317-
setChildrenOffset({ duration: `${params.durationLeave}ms` });
331+
setChildrenOffset({ duration: `${params.duration}ms` });
332+
318333
self.isActive = false;
319334
if (typeof params.onRotate === 'function') params.onRotate(0, 0);
320335
if (typeof params.onLeave === 'function') params.onLeave();
@@ -387,6 +402,7 @@ function Atropos(originalParams = {}) {
387402

388403
const destroy = () => {
389404
self.destroyed = true;
405+
cancelAnimationFrame(queueFrameId);
390406
$off(document, 'click', onDocumentClick);
391407
$off(eventsEl, 'pointerdown', onPointerEnter);
392408
$off(eventsEl, 'pointerenter', onPointerEnter);

src/react/atropos-react.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ const paramsKeys = [
77
'activeOffset',
88
'shadowOffset',
99
'shadowScale',
10-
'durationEnter',
11-
'durationLeave',
12-
'rotateLock',
10+
'duration',
1311
'rotate',
1412
'rotateTouch',
1513
'rotateXMax',

src/svelte/atropos-svelte.svelte

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@
1212
export let activeOffset = undefined;
1313
export let shadowOffset = undefined;
1414
export let shadowScale = undefined;
15-
export let durationEnter = undefined;
16-
export let durationLeave = undefined;
17-
export let rotateLock = undefined;
15+
export let duration = undefined;
1816
export let rotate = undefined;
1917
export let rotateTouch = undefined;
2018
export let rotateXMax = undefined;
@@ -42,9 +40,7 @@
4240
activeOffset,
4341
shadowOffset,
4442
shadowScale,
45-
durationEnter,
46-
durationLeave,
47-
rotateLock,
43+
duration,
4844
rotate,
4945
rotateTouch,
5046
rotateXMax,

src/vue/atropos-vue.d.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ declare const Atropos: DefineComponent<
1414
activeOffset: { type: NumberConstructor; default: undefined };
1515
shadowOffset: { type: NumberConstructor; default: undefined };
1616
shadowScale: { type: NumberConstructor; default: undefined };
17-
durationEnter: { type: NumberConstructor; default: undefined };
18-
durationLeave: { type: NumberConstructor; default: undefined };
19-
rotateLock: { type: BooleanConstructor; default: undefined };
17+
duration: { type: NumberConstructor; default: undefined };
2018
rotate: { type: BooleanConstructor; default: undefined };
2119
rotateTouch: { type: BooleanConstructor | StringConstructor; default: undefined };
2220
rotateXMax: { type: NumberConstructor; default: undefined };

src/vue/atropos-vue.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ const Atropos = {
1313
activeOffset: { type: Number, default: undefined },
1414
shadowOffset: { type: Number, default: undefined },
1515
shadowScale: { type: Number, default: undefined },
16-
durationEnter: { type: Number, default: undefined },
17-
durationLeave: { type: Number, default: undefined },
18-
rotateLock: { type: Boolean, default: undefined },
16+
duration: { type: Number, default: undefined },
1917
rotate: { type: Boolean, default: undefined },
2018
rotateTouch: { type: Boolean, default: undefined },
2119
rotateXMax: { type: Number, default: undefined },

0 commit comments

Comments
 (0)