Skip to content

Commit

Permalink
Fix transition velocity cutoff (#16)
Browse files Browse the repository at this point in the history
* Switch from using movementX/Y to diffs in offsetX/Y;
Reset velocity on pointer leave;
Reset velocity after 3 frames inside transition

* 0.4.6
  • Loading branch information
ydaniv authored Feb 16, 2025
1 parent ef476de commit 8ed7008
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 37 deletions.
29 changes: 20 additions & 9 deletions dist/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,8 @@ function getController$1 (config) {
};
}

const MOVEMENT_RESET_DELAY = 1e3 / 60 * 3; // == 50 (3 frames in 60fps)

/**
* @class Pointer
* @param {PointerConfig} config
Expand Down Expand Up @@ -409,15 +411,19 @@ class Pointer {
const DPR = shouldFixSynthPointer ? window.devicePixelRatio : 1;

const _measure = (event) => {
this.progress.x = this.config.root ? event.offsetX : event.x;
this.progress.y = this.config.root ? event.offsetY : event.y;
this.progress.vx = event.movementX;
this.progress.vy = event.movementY;
const newX = this.config.root ? event.offsetX : event.x;
const newY = this.config.root ? event.offsetY : event.y;
this.progress.vx = newX - this.progress.x;
this.progress.vy = newY - this.progress.y;
this.progress.x = newX;
this.progress.y = newY;
this._nextTick = trigger();
};

this._pointerLeave = () => {
this.progress.active = false;
this.progress.vx = 0;
this.progress.vy = 0;
this._nextTick = trigger();
};

Expand All @@ -434,8 +440,6 @@ class Pointer {
cancelable: true,
clientX: e.x * DPR,
clientY: e.y * DPR,
movementX: e.movementX * DPR,
movementY: e.movementY * DPR,
});

e.stopPropagation();
Expand Down Expand Up @@ -481,11 +485,18 @@ class Pointer {
const duration = this.config.transitionDuration;
const easing = this.config.transitionEasing || ((p) => p);
const now = performance.now();
let resetMovement = false;

const tick = (time) => {
const p = (time - this._startTime) / duration;
const t = easing(Math.min(1, p));

if (resetMovement) {
this.progress.vx = 0;
this.progress.vy = 0;
resetMovement = false;
}

this.currentProgress = Object.entries(this.progress).reduce((acc, [key, value]) => {
if (key === 'active') {
acc[key] = value;
Expand All @@ -497,9 +508,9 @@ class Pointer {

if (p < 1) {
this._nextTransitionTick = requestAnimationFrame(tick);
} else {
this.currentProgress.vx = 0;
this.currentProgress.vy = 0;

// reset movement on next frame
resetMovement = time - this._startTime > MOVEMENT_RESET_DELAY;
}

this.effect.tick(this.currentProgress);
Expand Down
23 changes: 14 additions & 9 deletions docs/demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@
#target {
box-sizing: border-box;
width: 40px;
margin: -20px 0 0 -20px;
/*margin: calc(25% - 20px) 0 0 calc(50% - 20px);*/
/*margin: -20px 0 0 -20px;*/
margin: calc(25% - 20px) 0 0 calc(50% - 20px);
aspect-ratio: 1;
border: 10px solid #007bff;
border-radius: 50%;
box-shadow:
0 0 6px rgb(0 0 0 / 50%),
inset 0 0 4px rgb(0 0 0 / 50%);
pointer-events: none;
/*transition: transform 1s linear;*/
/*transition: transform 0.5s linear;*/
}

#cover {
Expand Down Expand Up @@ -66,6 +66,7 @@
<pre id="y"></pre>
<pre id="vx"></pre> <pre id="vxmax"></pre>
<pre id="vy"></pre> <pre id="vymax"></pre>
<pre id="active"></pre>
</aside>
<section id="root">
<div id="target"></div>
Expand All @@ -84,11 +85,12 @@
const vxmax = document.getElementById('vxmax');
const vy = document.getElementById('vy');
const vymax = document.getElementById('vymax');
const active = document.getElementById('active');

let vxmaxValue = 0;
let vymaxValue = 0;

function log (p, v) {
function log (p, v, isActive) {
x.innerText = `X: ${p.x}`;
y.innerText = `Y: ${p.y}`;
vx.innerText = `Vx: ${v.x}`;
Expand All @@ -97,21 +99,24 @@
vymaxValue = Math.max(vymaxValue, v.y);
vxmax.innerText = `Vx Max: ${vxmaxValue}`;
vymax.innerText = `Vy Max: ${vymaxValue}`;
active.innerText = `Active: ${isActive}`;
}

const WIDTH = root.clientWidth;
const HEIGHT = root.clientHeight;
const pointer = new Pointer({
root,
allowActiveEvent: true,
scenes: [{
target,
effect: (scene, p, v) => {
log(p, v);
target.style.transform = `translate(${p.x * WIDTH}px, ${p.y * HEIGHT}px)`;
// target.style.transform = `translate(${v.x * 5}px, ${v.y * 5}px)`;
effect: (scene, p, v, isActive) => {
log(p, v, isActive);
// target.style.transform = `translate(${p.x * WIDTH}px, ${p.y * HEIGHT}px)`;
target.style.transform = `translate(${v.x * 5}px, ${v.y * 5}px)`;
}
}],
transitionDuration: 1000,
// noThrottle: true,
transitionDuration: 500,
transitionEasing: (t) => 1 - (1 - t) ** 2, // quadOut
});

Expand Down
29 changes: 20 additions & 9 deletions docs/demo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,8 @@ function getController$1 (config) {
};
}

const MOVEMENT_RESET_DELAY = 1e3 / 60 * 3; // 3 frames

/**
* @class Pointer
* @param {PointerConfig} config
Expand Down Expand Up @@ -407,15 +409,19 @@ class Pointer {
const DPR = shouldFixSynthPointer ? window.devicePixelRatio : 1;

const _measure = (event) => {
this.progress.x = this.config.root ? event.offsetX : event.x;
this.progress.y = this.config.root ? event.offsetY : event.y;
this.progress.vx = event.movementX;
this.progress.vy = event.movementY;
const newX = this.config.root ? event.offsetX : event.x;
const newY = this.config.root ? event.offsetY : event.y;
this.progress.vx = newX - this.progress.x;
this.progress.vy = newY - this.progress.y;
this.progress.x = newX;
this.progress.y = newY;
this._nextTick = trigger();
};

this._pointerLeave = () => {
this.progress.active = false;
this.progress.vx = 0;
this.progress.vy = 0;
this._nextTick = trigger();
};

Expand All @@ -432,8 +438,6 @@ class Pointer {
cancelable: true,
clientX: e.x * DPR,
clientY: e.y * DPR,
movementX: e.movementX * DPR,
movementY: e.movementY * DPR,
});

e.stopPropagation();
Expand Down Expand Up @@ -479,11 +483,18 @@ class Pointer {
const duration = this.config.transitionDuration;
const easing = this.config.transitionEasing || ((p) => p);
const now = performance.now();
let resetMovement = false;

const tick = (time) => {
const p = (time - this._startTime) / duration;
const t = easing(Math.min(1, p));

if (resetMovement) {
this.progress.vx = 0;
this.progress.vy = 0;
resetMovement = false;
}

this.currentProgress = Object.entries(this.progress).reduce((acc, [key, value]) => {
if (key === 'active') {
acc[key] = value;
Expand All @@ -495,9 +506,9 @@ class Pointer {

if (p < 1) {
this._nextTransitionTick = requestAnimationFrame(tick);
} else {
this.currentProgress.vx = 0;
this.currentProgress.vy = 0;

// reset movement on next frame
resetMovement = time - this._startTime > MOVEMENT_RESET_DELAY;
}

this.effect.tick(this.currentProgress);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kuliso",
"version": "0.4.5",
"version": "0.4.6",
"sideeffects": true,
"description": "Tiny library for performant pointer-driven or gyroscope-driven effects",
"main": "dist/index.cjs",
Expand Down
29 changes: 20 additions & 9 deletions src/Pointer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { getController } from './controller.js';
import { frameThrottle, testPointerOffsetDprBug } from './utilities.js';

const MOVEMENT_RESET_DELAY = 1e3 / 60 * 3; // == 50 (3 frames in 60fps)

/**
* @class Pointer
* @param {PointerConfig} config
Expand Down Expand Up @@ -66,15 +68,19 @@ export class Pointer {
const DPR = shouldFixSynthPointer ? window.devicePixelRatio : 1;

const _measure = (event) => {
this.progress.x = this.config.root ? event.offsetX : event.x;
this.progress.y = this.config.root ? event.offsetY : event.y;
this.progress.vx = event.movementX;
this.progress.vy = event.movementY;
const newX = this.config.root ? event.offsetX : event.x;
const newY = this.config.root ? event.offsetY : event.y;
this.progress.vx = newX - this.progress.x;
this.progress.vy = newY - this.progress.y;
this.progress.x = newX;
this.progress.y = newY;
this._nextTick = trigger();
};

this._pointerLeave = () => {
this.progress.active = false;
this.progress.vx = 0;
this.progress.vy = 0;
this._nextTick = trigger();
};

Expand All @@ -91,8 +97,6 @@ export class Pointer {
cancelable: true,
clientX: e.x * DPR,
clientY: e.y * DPR,
movementX: e.movementX * DPR,
movementY: e.movementY * DPR,
});

e.stopPropagation();
Expand Down Expand Up @@ -138,11 +142,18 @@ export class Pointer {
const duration = this.config.transitionDuration;
const easing = this.config.transitionEasing || ((p) => p);
const now = performance.now();
let resetMovement = false;

const tick = (time) => {
const p = (time - this._startTime) / duration;
const t = easing(Math.min(1, p));

if (resetMovement) {
this.progress.vx = 0;
this.progress.vy = 0;
resetMovement = false;
}

this.currentProgress = Object.entries(this.progress).reduce((acc, [key, value]) => {
if (key === 'active') {
acc[key] = value;
Expand All @@ -154,9 +165,9 @@ export class Pointer {

if (p < 1) {
this._nextTransitionTick = requestAnimationFrame(tick);
} else {
this.currentProgress.vx = 0;
this.currentProgress.vy = 0;

// reset movement on next frame
resetMovement = time - this._startTime > MOVEMENT_RESET_DELAY;
}

this.effect.tick(this.currentProgress);
Expand Down

0 comments on commit 8ed7008

Please sign in to comment.