Skip to content

Commit

Permalink
Initial work
Browse files Browse the repository at this point in the history
  • Loading branch information
ydaniv committed Nov 30, 2023
1 parent 1284f9f commit 909206f
Show file tree
Hide file tree
Showing 11 changed files with 489 additions and 3 deletions.
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
package-lock.json
.idea/
coverage/
.DS_Store
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
registry="https://registry.npmjs.org/"
6 changes: 3 additions & 3 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)
MIT License

Copyright (c) 2015 Wix.com
Copyright (c) 2023 Wix.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand All @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
14 changes: 14 additions & 0 deletions documentation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
toc:
- Kuliso
- mouseConfig
- MouseScene
- EffectCallback
- RangeOffset
- RangeName
- CSSUnitValue
- AbsoluteOffsetContext
- name: utilities
children:
- lerp
- defaultTo
- frameThrottle
41 changes: 41 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "kuliso",
"version": "0.0.1",
"description": "Tiny library for performant pointer-driven or gyroscope-driven effects",
"main": "dist/index.cjs",
"module": "index.js",
"types": "types.d.ts",
"type": "module",
"scripts": {
"build": "rollup -c",
"test": "c8 ava test/*.spec.js -s",
"test:debug": "ava test/*.spec.js -s",
"docs": "documentation build src/Kuliso.js -f html -o docs/reference -c documentation.yml",
"rtfm": "npm run docs && http-server ./docs/reference"
},
"repository": {
"type": "git",
"url": "git+https://github.com/wix-incubator/kuliso.git"
},
"keywords": [
"animation",
"js",
"pointer",
"gyroscope"
],
"author": "wow!Team",
"license": "MIT",
"bugs": {
"url": "https://github.com/wix-incubator/kuliso/issues"
},
"homepage": "https://github.com/wix-incubator/kuliso#readme",
"devDependencies": {
"ava": "^5.3.1",
"c8": "^8.0.1",
"documentation": "^14.0.2",
"http-server": "^14.1.1",
"rollup": "^4.6.0",
"rollup-plugin-filesize": "^10.0.0",
"rollup-plugin-progress": "^1.1.2"
}
}
16 changes: 16 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import progress from 'rollup-plugin-progress';
import filesize from 'rollup-plugin-filesize';

export default {
input: 'index.js',
output: {
file: 'dist/index.cjs',
format: 'cjs'
},
plugins: [
progress({
clearLine: false
}),
filesize()
]
};
241 changes: 241 additions & 0 deletions src/Kuliso.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
import { getController } from './controller.js';
import { defaultTo, frameThrottle, lerp } from './utilities.js';

/**
* @private
*/
const DEFAULTS = {
transitionActive: false,
transitionFriction: 0.9,
transitionEpsilon: 1,
velocityActive: false,
velocityMax: 1
};

/**
* @class Kuliso
* @param {mouseConfig} config
*
* @example
* import { Kuliso } from 'kuliso';
*
* const kuliso = new Kuliso({
* scenes: [...]
* });
* kuliso.start();
*/
export class Kuliso {
constructor (config = {}) {
this.config = defaultTo(config, DEFAULTS);

this.progress = {
p: 0,
prevP: 0,
vp: 0
};
this.currentProgress = {
p: 0,
prevP: 0,
vp: 0
};

this._lerpFrameId = 0;
this.effect = null;
// if no root or root is document.body then use window
this.config.root = (!this.config.root || this.config.root === window.document.body) ? window : this.config.root;
this.config.resetProgress = this.config.resetProgress || this.resetProgress.bind(this);

this._measure = this.config.measure || (() => {
const root = this.config.root;
// get current scroll position from window or element
this.progress.p = this.config.horizontal
? root.scrollX || root.scrollLeft || 0
: root.scrollY || root.scrollTop || 0;
});

this._trigger = frameThrottle(() => {
this._measure?.();
this.tick(true);
});
}

/**
* Setup event and effect, and reset progress and frame.
*/
start () {
this.setupEffect();
this.setupEvent();
this.resetProgress();
this.tick();
}

/**
* Removes event listener.
*/
pause () {
this.removeEvent();
}

/**
* Reset progress in the DOM and inner state to given x and y.
*
* @param {Object} [pointerPosition]
* @param {number} [pointerPosition.x]
* @param {number} [pointerPosition.y]
*/
resetProgress (pointerPosition = {}) {
// get current pointer position (support window, element)
const root = this.config.root;
const x = pointerPosition.x || pointerPosition.x === 0 ? pointerPosition.x : root.scrollX || root.scrollLeft || 0;
const y = pointerPosition.y || pointerPosition.y === 0 ? pointerPosition.y : root.scrollY || root.scrollTop || 0;
this.progress.x = x;
this.progress.y = y;
this.progress.prevX = x;
this.progress.prevY = y;
this.progress.vx = 0;
this.progress.vy = 0;

if (pointerPosition) {
this.config.root.scrollTo(x, y);
}
}

/**
* Handle animation frame work.
*
* @param {boolean} [clearLerpFrame] whether to cancel an existing lerp frame
*/
tick (clearLerpFrame) {
const hasLerp = this.config.transitionActive;

// if transition is active interpolate to next point
if (hasLerp) {
this.lerp();
}

// choose the object we iterate on
const progress = hasLerp ? this.currentProgress : this.progress;

if (this.config.velocityActive) {
const dp = progress.p - progress.prevP;
const factorP = dp < 0 ? -1 : 1;
progress.vp = Math.min(this.config.velocityMax, Math.abs(dp)) / this.config.velocityMax * factorP;
}

// update effect
this.effect.tick(progress);

if (hasLerp && (progress.p !== this.progress.p)) {
if (clearLerpFrame && this._lerpFrameId) {
window.cancelAnimationFrame(this._lerpFrameId);
}

this._lerpFrameId = window.requestAnimationFrame(() => this.tick());
}

progress.prevP = progress.p;
}

/**
* Calculate current progress.
*/
lerp () {
this.currentProgress.p = lerp(this.currentProgress.p, this.progress.p, +(1 - this.config.transitionFriction).toFixed(3), this.config.transitionEpsilon);
}

/**
* Stop the event and effect, and remove all DOM side-effects.
*/
destroy () {
this.pause();
this.removeEffect();
}

/**
* Register to pointermove for triggering update.
*/
setupEvent () {
this.removeEvent();
this.config.root.addEventListener('pointermove', this._trigger);
}

/**
* Remove pointermove handler.
*/
removeEvent () {
this.config.root.removeEventListener('pointermove', this._trigger);
}

/**
* Reset registered effect.
*/
setupEffect () {
this.removeEffect();
this.effect = getController(this.config);
}

/**
* Remove registered effect.
*/
removeEffect () {
this.effect && this.effect.destroy();
this.effect = null;
}
}

/**
* @typedef {object} scrollConfig
* @property {ScrollScene[]} scenes list of effect scenes to perform during scroll.
* @property {boolean} [horizontal] whether to use the horizontal axis. Defaults to `false`.
* @property {boolean} [transitionActive] whether to animate effect progress.
* @property {number} [transitionFriction] between 0 to 1, amount of friction effect in the transition. 1 being no movement and 0 as no friction. Defaults to 0.4.
* @property {boolean} [velocityActive] whether to calculate velocity with progress.
* @property {number} [velocityMax] max possible value for velocity. Velocity value will be normalized according to this number, so it is kept between 0 and 1. Defaults to 1.
* @property {boolean} [observeViewportEntry] whether to observe entry/exit of scenes into viewport for disabling/enabling them. Defaults to `true`.
* @property {boolean} [viewportRootMargin] `rootMargin` option to be used for viewport observation. Defaults to `'7% 7%'`.
* @property {boolean} [observeViewportResize] whether to observe resize of the visual viewport. Defaults to `false`.
* @property {boolean} [observeSourcesResize] whether to observe resize of view-timeline source elements. Defaults to `false`.
* @property {Element|Window} [root] the scrollable element, defaults to window.
*/

/**
* @typedef {Object} ScrollScene
* @desc A configuration object for a scene. Must be provided an effect function, and either a start and end, a start and duration, or a duration as RangeName.
* @example { effects: (scene, p) => { animation.currentTime = p; }, duration: 'contain' }
* @property {EffectCallback} effect the effect to perform.
* @property {number|RangeOffset} start scroll position in pixels where effect starts.
* @property {number|RangeName} [duration] duration of effect in pixels. Defaults to end - start.
* @property {number|RangeOffset} [end] scroll position in pixels where effect ends. Defaults to start + duration.
* @property {boolean} [disabled] whether to perform updates on the scene. Defaults to false.
* @property {Element} [viewSource] an element to be used for observing intersection with viewport for disabling/enabling the scene, or the source of a ViewTimeline if scene start/end are provided as ranges.
*/

/**
* @typedef {function(scene: ScrollScene, progress: number, velocity: number): void} EffectCallback
* @param {ScrollScene} scene
* @param {number} progress
* @param {number} velocity
*/

/**
* @typedef {'entry' | 'contain' | 'exit' | 'cover'} RangeName
*/

/**
* @typedef {Object} RangeOffset
* @property {RangeName} name
* @property {number} offset
* @property {CSSUnitValue} [add]
*/

/**
* @typedef {Object} CSSUnitValue
* @property {number} value
* @property {'px'|'vh'|'vw'} unit
*/

/**
* @typedef {Object} AbsoluteOffsetContext
* @property {number} viewportWidth
* @property {number} viewportHeight
*/
Loading

0 comments on commit 909206f

Please sign in to comment.