18 Commits

Author SHA1 Message Date
Alex Fox
33e026efc5 Merge branch 'dev' into ts-upgrade 2021-10-16 09:21:42 +01:00
Alex Fox
489ca6d3f1 build version 2021-10-15 21:14:24 +01:00
Alex Fox
ec5180c890 Merge branch 'Pckool-dev' into ts-upgrade 2021-10-15 21:11:19 +01:00
Alex Fox
3d3afe50dc Merge branch 'dev' of https://github.com/Pckool/lax.js into Pckool-dev 2021-10-15 21:11:11 +01:00
Pckool
ac9f55c864 Fix for global.lax exposing the entire class 2021-02-10 00:10:15 -05:00
Pckool
6f97153302 Revert "Finally finished types for everything"
This reverts commit c11bf52ec4.
2021-02-10 00:01:48 -05:00
Pckool
5934da75ca Merged generated module into one file! 2021-02-09 23:00:08 -05:00
Pckool
c11bf52ec4 Finally finished types for everything 2021-02-09 22:39:31 -05:00
Pckool
d96e295da8 More progress on typing 2021-02-09 22:12:44 -05:00
Pckool
6b7317dfcf Fix for type errors with preset functions 2021-02-09 21:22:45 -05:00
Pckool
06ca48f3b4 Major type improvements/progress 2021-02-09 20:01:43 -05:00
Pckool
33812bd064 Starting to remove all "any" types in favor of strong types 2021-02-09 16:56:06 -05:00
Pckool
b223f14885 Separated out the tpes into a new file 2021-02-09 16:11:00 -05:00
Pckool
06ad981bf9 New commit 2021-02-09 15:18:29 -05:00
Pckool
d76581ca49 Fix for several type issues 2021-02-05 03:51:35 -05:00
Pckool
87cd7d1efc Decided to only expose functions from the Lax instance that the user will need. 2021-02-05 01:44:32 -05:00
Pckool
e68947bf7c Fix for misnamed types file 2021-02-05 00:29:24 -05:00
Pckool
fe62c7698c typescript upgrade 2021-02-05 00:01:27 -05:00
19 changed files with 2242 additions and 1921 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
node_modules/
.DS_Store
LaxLogo.sketch
LaxLogo.sketch
tsconfig.tsbuildinfo

2
docs/lib/lax.min.js vendored

File diff suppressed because one or more lines are too long

102
lib/lax.d.ts vendored Normal file
View File

@@ -0,0 +1,102 @@
import { DriverOptions, LaxPresetFn, ElementOptions, DriverTransforms } from './types';
declare class Lax {
private drivers;
private elements;
private frame;
private debug;
windowWidth: number;
windowHeight: number;
presets: {
fadeIn: LaxPresetFn;
fadeOut: LaxPresetFn;
fadeInOut: LaxPresetFn;
scaleIn: LaxPresetFn;
scaleOut: LaxPresetFn;
scaleInOut: LaxPresetFn;
slideX: LaxPresetFn;
slideY: LaxPresetFn;
jiggle: LaxPresetFn;
seesaw: LaxPresetFn;
zigzag: LaxPresetFn;
hueRotate: LaxPresetFn;
spin: LaxPresetFn;
flipX: LaxPresetFn;
flipY: LaxPresetFn;
blurIn: LaxPresetFn;
blurOut: LaxPresetFn;
blurInOut: LaxPresetFn;
};
private debugData;
init: () => void;
onWindowResize: () => void;
onAnimationFrame: (e: DOMHighResTimeStamp) => void;
addDriver: (name: string, getValueFn: () => number, options?: DriverOptions) => void;
removeDriver: (name: string) => void;
findAndAddElements: () => void;
addElements: (selector: string, transforms: DriverTransforms, options?: ElementOptions) => void;
removeElements: (selector: string) => void;
addElement: (domElement: HTMLElement | Element, transforms: DriverTransforms, options?: ElementOptions) => void;
removeElement: (domElement: HTMLElement | Element) => void;
}
export declare const laxInstance: Lax;
declare const lax: {
addElements: (selector: string, transforms: DriverTransforms, options?: ElementOptions) => void;
removeElements: (selector: string) => void;
removeElement: (domElement: HTMLElement | Element) => void;
addElement: (domElement: HTMLElement | Element, transforms: DriverTransforms, options?: ElementOptions) => void;
init: () => void;
addDriver: (name: string, getValueFn: () => number, options?: DriverOptions) => void;
removeDriver: (name: string) => void;
presets: {
fadeIn: LaxPresetFn;
fadeOut: LaxPresetFn;
fadeInOut: LaxPresetFn;
scaleIn: LaxPresetFn;
scaleOut: LaxPresetFn;
scaleInOut: LaxPresetFn;
slideX: LaxPresetFn;
slideY: LaxPresetFn;
jiggle: LaxPresetFn;
seesaw: LaxPresetFn;
zigzag: LaxPresetFn;
hueRotate: LaxPresetFn;
spin: LaxPresetFn;
flipX: LaxPresetFn;
flipY: LaxPresetFn;
blurIn: LaxPresetFn;
blurOut: LaxPresetFn;
blurInOut: LaxPresetFn;
};
};
declare global {
namespace NodeJS {
interface Global {
lax: typeof lax;
}
}
interface Window {
lax: typeof lax;
}
}
export default lax;
export declare const addDriver: (name: string, getValueFn: () => number, options?: DriverOptions) => void, addElement: (domElement: HTMLElement | Element, transforms: DriverTransforms, options?: ElementOptions) => void, addElements: (selector: string, transforms: DriverTransforms, options?: ElementOptions) => void, removeDriver: (name: string) => void, removeElement: (domElement: HTMLElement | Element) => void, removeElements: (selector: string) => void, init: () => void, presets: {
fadeIn: LaxPresetFn;
fadeOut: LaxPresetFn;
fadeInOut: LaxPresetFn;
scaleIn: LaxPresetFn;
scaleOut: LaxPresetFn;
scaleInOut: LaxPresetFn;
slideX: LaxPresetFn;
slideY: LaxPresetFn;
jiggle: LaxPresetFn;
seesaw: LaxPresetFn;
zigzag: LaxPresetFn;
hueRotate: LaxPresetFn;
spin: LaxPresetFn;
flipX: LaxPresetFn;
flipY: LaxPresetFn;
blurIn: LaxPresetFn;
blurOut: LaxPresetFn;
blurInOut: LaxPresetFn;
};
//# sourceMappingURL=lax.d.ts.map

1
lib/lax.d.ts.map Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"lax.d.ts","sourceRoot":"","sources":["../src/lax.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAkE,MAAM,SAAS,CAAA;AA2ftJ,cAAM,GAAG;IACP,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,KAAK,CAAI;IAEjB,OAAO,CAAC,KAAK,CAAQ;IAErB,WAAW,SAAI;IACf,YAAY,SAAI;IAChB,OAAO;;;;;;;;;;;;;;;;;;;MAAa;IAEpB,OAAO,CAAC,SAAS,CAGhB;IAED,IAAI,aAQH;IAED,cAAc,aASb;IAED,gBAAgB,MAAO,mBAAmB,UA4BzC;IAED,SAAS,SAAU,MAAM,cAAc,MAAM,MAAM,YAAW,aAAa,UAI1E;IAED,YAAY,SAAU,MAAM,UAE3B;IAED,kBAAkB,aAwBjB;IAED,WAAW,aAAc,MAAM,cAAc,gBAAgB,YAAY,cAAc,UAMtF;IAED,cAAc,aAAc,MAAM,UAEjC;IAED,UAAU,eAAgB,WAAW,GAAG,OAAO,cAAc,gBAAgB,YAAY,cAAc,UAEtG;IAED,aAAa,eAAgB,WAAW,GAAG,OAAO,UAEjD;CACF;AAED,eAAO,MAAM,WAAW,KAAY,CAAA;AACpC,QAAA,MAAM,GAAG;4BAtBkB,MAAM,cAAc,gBAAgB,YAAY,cAAc;+BAQ3D,MAAM;gCAQL,WAAW,GAAG,OAAO;6BAJxB,WAAW,GAAG,OAAO,cAAc,gBAAgB,YAAY,cAAc;;sBAhDpF,MAAM,cAAc,MAAM,MAAM,YAAW,aAAa;yBAMrD,MAAM;;;;;;;;;;;;;;;;;;;;;CA6D7B,CAAA;AAGD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM,CAAC;QACf,UAAU,MAAM;YACd,GAAG,EAAE,OAAO,GAAG,CAAA;SAChB;KACF;IACD,UAAU,MAAM;QACd,GAAG,EAAE,OAAO,GAAG,CAAA;KAChB;CACF;AAWD,eAAe,GAAG,CAAA;AAClB,eAAO,MAAQ,SAAS,SA3FH,MAAM,cAAc,MAAM,MAAM,YAAW,aAAa,WA2FnD,UAAU,eA3CR,WAAW,GAAG,OAAO,cAAc,gBAAgB,YAAY,cAAc,WA2CnE,WAAW,aAvDtB,MAAM,cAAc,gBAAgB,YAAY,cAAc,WAuDtC,YAAY,SArFvC,MAAM,WAqFmC,aAAa,eAvC/C,WAAW,GAAG,OAAO,WAuC4B,cAAc,aA/ChE,MAAM,WA+C4D,IAAI,cAAE,OAAO;;;;;;;;;;;;;;;;;;;CAAQ,CAAA"}

1254
lib/lax.js

File diff suppressed because it is too large Load Diff

1
lib/lax.js.map Normal file

File diff suppressed because one or more lines are too long

2
lib/lax.min.js vendored

File diff suppressed because one or more lines are too long

Binary file not shown.

1
lib/lax.min.js.map Normal file

File diff suppressed because one or more lines are too long

72
lib/types.d.ts vendored Normal file
View File

@@ -0,0 +1,72 @@
export interface DriverOptions {
inertiaEnabled?: boolean;
frameStep?: number;
}
export interface StyleObject {
[key: string]: any;
}
export declare type LaxPresetName = "fadeIn" | "fadeOut" | "fadeInOut" | "scaleIn" | "scaleOut" | "scaleInOut" | "slideX" | "slideY" | "jiggle" | "seesaw" | "zigzag" | "hueRotate" | "spin" | "flipX" | "flipY" | "blurIn" | "blurOut" | "blurInOut";
export declare type LaxPresetFn = (x: number | string, y: number | string) => LaxPresetStyleProps;
export declare type easingOptions = "easeInQuad" | "easeOutQuad" | "easeInOutQuad" | "easeInCubic" | "easeOutCubic" | "easeInOutCubic" | "easeInQuart" | "easeOutQuart" | "easeInOutQuart" | "easeInQuint" | "easeOutQuint" | "easeInOutQuint" | "easeOutBounce" | "easeInBounce" | "easeOutBack" | "easeInBack";
export declare type specialValues = "screenWidth" | "screenHeight" | "pageWidth" | "pageHeight" | "elWidth" | "elHeight" | "elInY" | "elOutY" | "elCenterY" | "elInX" | "elOutX" | "elCenterX" | "index";
export declare enum cssValues {
"opacity" = "opacity",
"scaleX" = "scaleX",
"scaleY" = "scaleY",
"scale" = "scale",
"skewX" = "skewX",
"skewY" = "skewY",
"skew" = "skew",
"rotateX" = "rotateX",
"rotateY" = "rotateY",
"rotate" = "rotate",
"translateX" = "translateX",
"translateY" = "translateY",
"translateZ" = "translateZ",
"blur" = "blur",
"hue-rotate" = "hue-rotate",
"brightness" = "brightness"
}
export interface LaxStyleMapOptions {
modValue?: number | undefined;
frameStep?: number;
inertia?: number;
inertiaMode?: "normal" | "absolute";
cssUnit?: string;
cssFn?(value: number, domElement: HTMLElement | Element): number | string;
easing?: easingOptions;
}
export declare type LaxStyleMap = [
Array<number | specialValues | string>,
Array<number | specialValues | string>,
LaxStyleMapOptions?
];
export interface LaxStyleProps extends LaxPresetStyleProps {
"presets"?: Array<LaxPresetName>;
}
export interface LaxPresetStyleProps {
"opacity"?: LaxStyleMap;
"scaleX"?: LaxStyleMap;
"scaleY"?: LaxStyleMap;
"scale"?: LaxStyleMap;
"skewX"?: LaxStyleMap;
"skewY"?: LaxStyleMap;
"skew"?: LaxStyleMap;
"rotateX"?: LaxStyleMap;
"rotateY"?: LaxStyleMap;
"rotate"?: LaxStyleMap;
"translateX"?: LaxStyleMap;
"translateY"?: LaxStyleMap;
"translateZ"?: LaxStyleMap;
"blur"?: LaxStyleMap;
"hue-rotate"?: LaxStyleMap;
"brightness"?: LaxStyleMap;
}
export interface ElementOptions {
style?: StyleObject;
onUpdate?(driverValues: any, domElement: HTMLElement | Element): void;
}
export interface DriverTransforms {
[key: string]: LaxStyleProps | LaxPresetStyleProps;
}
//# sourceMappingURL=types.d.ts.map

1
lib/types.d.ts.map Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}

23
lib/types.js Normal file
View File

@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.cssValues = void 0;
var cssValues;
(function (cssValues) {
cssValues["opacity"] = "opacity";
cssValues["scaleX"] = "scaleX";
cssValues["scaleY"] = "scaleY";
cssValues["scale"] = "scale";
cssValues["skewX"] = "skewX";
cssValues["skewY"] = "skewY";
cssValues["skew"] = "skew";
cssValues["rotateX"] = "rotateX";
cssValues["rotateY"] = "rotateY";
cssValues["rotate"] = "rotate";
cssValues["translateX"] = "translateX";
cssValues["translateY"] = "translateY";
cssValues["translateZ"] = "translateZ";
cssValues["blur"] = "blur";
cssValues["hue-rotate"] = "hue-rotate";
cssValues["brightness"] = "brightness";
})(cssValues = exports.cssValues || (exports.cssValues = {}));
//# sourceMappingURL=types.js.map

1
lib/types.js.map Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AA+DA,IAAY,SAiBX;AAjBD,WAAY,SAAS;IACnB,gCAAmB,CAAA;IACnB,8BAAiB,CAAA;IACjB,8BAAiB,CAAA;IACjB,4BAAe,CAAA;IACf,4BAAe,CAAA;IACf,4BAAe,CAAA;IACf,0BAAa,CAAA;IACb,gCAAmB,CAAA;IACnB,gCAAmB,CAAA;IACnB,8BAAiB,CAAA;IACjB,sCAAyB,CAAA;IACzB,sCAAyB,CAAA;IACzB,sCAAyB,CAAA;IACzB,0BAAa,CAAA;IACb,sCAAyB,CAAA;IACzB,sCAAyB,CAAA;AAC3B,CAAC,EAjBW,SAAS,GAAT,iBAAS,KAAT,iBAAS,QAiBpB"}

View File

@@ -1,14 +1,18 @@
{
"name": "lax.js",
"version": "2.0.3",
"version": "2.1.0",
"scripts": {
"build": "babel src -d lib && uglifyjs lib/lax.js -o lib/lax.min.js -c -m && gzip -c lib/lax.min.js > lib/lax.min.js.gz && cp lib/lax.min.js docs/lib/lax.min.js"
"prebuild-ts": "tsc",
"build-ts": "uglifyjs ./lib/lax.js --source-map -o ./lib/lax.min.js -c -m && gzip -c ./lib/lax.min.js > ./lib/lax.min.js.gz && cp ./lib/lax.min.js ./docs/lib/lax.min.js",
"dev": "tsc --watch"
},
"devDependencies": {
"@babel/cli": "^7.12.1",
"@babel/core": "^7.12.3",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/preset-env": "^7.12.1",
"@types/node": "^14.14.25",
"typescript": "^4.1.3",
"uglify-js": "^3.9.4"
},
"description": "Simple & lightweight (<4kb gzipped) vanilla JavaScript library to create smooth & beautiful animations when you scroll.",
@@ -18,7 +22,8 @@
"type": "git",
"url": "https://github.com/alexfoxy/lax.js"
},
"main": "lib/lax.min.js",
"main": "lib/lax.js",
"types": "lib/lax.d.ts",
"keywords": [
"javascript",
"lax",

View File

@@ -1,616 +0,0 @@
(() => {
const inOutMap = (y = 30) => {
return ["elInY+elHeight", `elCenterY-${y}`, "elCenterY", `elCenterY+${y}`, "elOutY-elHeight"]
}
const laxPresets = {
fadeInOut: (y = 30, str = 0) => ({
"opacity": [
inOutMap(y),
[str, 1, 1, 1, str]
],
}),
fadeIn: (y = 'elCenterY', str = 0) => ({
"opacity": [
["elInY+elHeight", y],
[str, 1],
],
}),
fadeOut: (y = 'elCenterY', str = 0) => ({
"opacity": [
[y, "elOutY-elHeight"],
[1, str],
],
}),
blurInOut: (y = 100, str = 20) => ({
"blur": [
inOutMap(y),
[str, 0, 0, 0, str],
],
}),
blurIn: (y = 'elCenterY', str = 20) => ({
"blur": [
["elInY+elHeight", y],
[str, 0],
],
}),
blurOut: (y = 'elCenterY', str = 20) => ({
"opacity": [
[y, "elOutY-elHeight"],
[0, str],
],
}),
scaleInOut: (y = 100, str = 0.6) => ({
"scale": [
inOutMap(y),
[str, 1, 1, 1, str],
],
}),
scaleIn: (y = 'elCenterY', str = 0.6) => ({
"scale": [
["elInY+elHeight", y],
[str, 1],
],
}),
scaleOut: (y = 'elCenterY', str = 0.6) => ({
"scale": [
[y, "elOutY-elHeight"],
[1, str],
],
}),
slideX: (y = 0, str = 500) => ({
"translateX": [
['elInY', y],
[0, str],
],
}),
slideY: (y = 0, str = 500) => ({
"translateY": [
['elInY', y],
[0, str],
],
}),
spin: (y = 1000, str = 360) => ({
"rotate": [
[0, y],
[0, str],
{
modValue: y,
}
],
}),
flipX: (y = 1000, str = 360) => ({
"rotateX": [
[0, y],
[0, str],
{
modValue: y
}
],
}),
flipY: (y = 1000, str = 360) => ({
"rotateY": [
[0, y],
[0, str],
{
modValue: y
}
],
}),
jiggle: (y = 50, str = 40) => ({
"skewX": [
[0, y * 1, y * 2, y * 3, y * 4],
[0, str, 0, -str, 0],
{
modValue: y * 4,
}
],
}),
seesaw: (y = 50, str = 40) => ({
"skewY": [
[0, y * 1, y * 2, y * 3, y * 4],
[0, str, 0, -str, 0],
{
modValue: y * 4,
}
],
}),
zigzag: (y = 100, str = 100) => ({
"translateX": [
[0, y * 1, y * 2, y * 3, y * 4],
[0, str, 0, -str, 0],
{
modValue: y * 4,
}
],
}),
hueRotate: (y = 600, str = 360) => ({
"hue-rotate": [
[0, y],
[0, str],
{
modValue: y,
}
],
}),
}
const laxInstance = (() => {
const transformKeys = ["perspective", "scaleX", "scaleY", "scale", "skewX", "skewY", "skew", "rotateX", "rotateY", "rotate"]
const filterKeys = ["blur", "hue-rotate", "brightness"]
const translate3dKeys = ["translateX", "translateY", "translateZ"]
const pxUnits = ["perspective", "border-radius", "blur", "translateX", "translateY", "translateZ"]
const degUnits = ["hue-rotate", "rotate", "rotateX", "rotateY", "skew", "skewX", "skewY"]
function getArrayValues(arr, windowWidth) {
if (Array.isArray(arr)) return arr
const keys = Object.keys(arr).map(x => parseInt(x)).sort((a, b) => a > b ? 1 : -1)
let retKey = keys[keys.length - 1]
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if (windowWidth < key) {
retKey = key
break
}
}
return arr[retKey]
}
function lerp(start, end, t) {
return start * (1 - t) + end * t
}
function invlerp(a, b, v) {
return (v - a) / (b - a)
}
function interpolate(arrA, arrB, v, easingFn) {
let k = 0
arrA.forEach((a) => {
if (a < v) k++
})
if (k <= 0) {
return arrB[0]
}
if (k >= arrA.length) {
return arrB[arrA.length - 1]
}
const j = k - 1
let vector = invlerp(arrA[j], arrA[k], v)
if (easingFn) vector = easingFn(vector)
const lerpVal = lerp(arrB[j], arrB[k], vector)
return lerpVal
}
const easings = {
easeInQuad: t => t * t,
easeOutQuad: t => t * (2 - t),
easeInOutQuad: t => t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
easeInCubic: t => t * t * t,
easeOutCubic: t => (--t) * t * t + 1,
easeInOutCubic: t => t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
easeInQuart: t => t * t * t * t,
easeOutQuart: t => 1 - (--t) * t * t * t,
easeInOutQuart: t => t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t,
easeInQuint: t => t * t * t * t * t,
easeOutQuint: t => 1 + (--t) * t * t * t * t,
easeInOutQuint: t => t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t,
easeOutBounce: t => {
const n1 = 7.5625
const d1 = 2.75
if (t < 1 / d1) {
return n1 * t * t
} else if (t < 2 / d1) {
return n1 * (t -= 1.5 / d1) * t + 0.75
} else if (t < 2.5 / d1) {
return n1 * (t -= 2.25 / d1) * t + 0.9375
} else {
return n1 * (t -= 2.625 / d1) * t + 0.984375
}
},
easeInBounce: t => {
return 1 - easings.easeOutBounce(1 - t)
},
easeOutBack: t => {
const c1 = 1.70158
const c3 = c1 + 1
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2)
},
easeInBack: t => {
const c1 = 1.70158
const c3 = c1 + 1
return c3 * t * t * t - c1 * t * t
},
}
function flattenStyles(styles) {
const flattenedStyles = {
transform: '',
filter: ''
}
const translate3dValues = {
translateX: 0.00001,
translateY: 0.00001,
translateZ: 0.00001
}
Object.keys(styles).forEach((key) => {
const val = styles[key]
const unit = pxUnits.includes(key) ? 'px' : (degUnits.includes(key) ? 'deg' : '')
if (translate3dKeys.includes(key)) {
translate3dValues[key] = val
} else if (transformKeys.includes(key)) {
flattenedStyles.transform += `${key}(${val}${unit}) `
} else if (filterKeys.includes(key)) {
flattenedStyles.filter += `${key}(${val}${unit}) `
} else {
flattenedStyles[key] = `${val}${unit} `
}
})
flattenedStyles.transform = `translate3d(${translate3dValues.translateX}px, ${translate3dValues.translateY}px, ${translate3dValues.translateZ}px) ${flattenedStyles.transform}`
return flattenedStyles
}
// https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY#Notes
function getScrollPosition() {
const supportPageOffset = window.pageXOffset !== undefined
const isCSS1Compat = ((document.compatMode || '') === 'CSS1Compat')
const x = supportPageOffset ? window.pageXOffset : isCSS1Compat ? document.documentElement.scrollLeft : document.body.scrollLeft
const y = supportPageOffset ? window.pageYOffset : isCSS1Compat ? document.documentElement.scrollTop : document.body.scrollTop
return [y, x]
}
function parseValue(val, { width, height, x, y }, index) {
if (typeof val === 'number') {
return val
}
const pageHeight = document.body.scrollHeight
const pageWidth = document.body.scrollWidth
const screenWidth = window.innerWidth
const screenHeight = window.innerHeight
const [scrollTop, scrollLeft] = getScrollPosition()
const left = x + scrollLeft
const right = left + width
const top = y + scrollTop
const bottom = top + height
return Function(`return ${val
.replace(/screenWidth/g, screenWidth)
.replace(/screenHeight/g, screenHeight)
.replace(/pageHeight/g, pageHeight)
.replace(/pageWidth/g, pageWidth)
.replace(/elWidth/g, width)
.replace(/elHeight/g, height)
.replace(/elInY/g, top - screenHeight)
.replace(/elOutY/g, bottom)
.replace(/elCenterY/g, top + (height / 2) - (screenHeight / 2))
.replace(/elInX/g, left - screenWidth)
.replace(/elOutX/g, right)
.replace(/elCenterX/g, left + (width / 2) - (screenWidth / 2))
.replace(/index/g, index)
}`)()
}
class LaxDriver {
getValueFn
name = ''
lastValue = 0
frameStep = 1
m1 = 0
m2 = 0
inertia = 0
inertiaEnabled = false
constructor(name, getValueFn, options = {}) {
this.name = name
this.getValueFn = getValueFn
Object.keys(options).forEach((key) => {
this[key] = options[key]
})
this.lastValue = this.getValueFn(0)
}
getValue = (frame) => {
let value = this.lastValue
if (frame % this.frameStep === 0) {
value = this.getValueFn(frame)
}
if (this.inertiaEnabled) {
const delta = value - this.lastValue
const damping = 0.8
this.m1 = this.m1 * damping + delta * (1 - damping)
this.m2 = this.m2 * damping + this.m1 * (1 - damping)
this.inertia = Math.round(this.m2 * 5000) / 15000
}
this.lastValue = value
return [this.lastValue, this.inertia]
}
}
class LaxElement {
domElement
transformsData
styles = {}
selector = ''
groupIndex = 0
laxInstance
onUpdate
constructor(selector, laxInstance, domElement, transformsData, groupIndex = 0, options = {}) {
this.selector = selector
this.laxInstance = laxInstance
this.domElement = domElement
this.transformsData = transformsData
this.groupIndex = groupIndex
const { style = {}, onUpdate } = options
Object.keys(style).forEach(key => {
domElement.style.setProperty(key, style[key])
})
if (onUpdate) this.onUpdate = onUpdate
this.calculateTransforms()
}
update = (driverValues, frame) => {
const { transforms } = this
const styles = {}
for (let driverName in transforms) {
const styleBindings = transforms[driverName]
if (!driverValues[driverName]) {
console.error("No lax driver with name: ", driverName)
}
const [value, inertiaValue] = driverValues[driverName]
for (let key in styleBindings) {
const [arr1, arr2, options = {}] = styleBindings[key]
const { modValue, frameStep = 1, easing, inertia, inertiaMode, cssFn, cssUnit = '' } = options
const easingFn = easings[easing]
if (frame % frameStep === 0) {
const v = modValue ? value % modValue : value
let interpolatedValue = interpolate(arr1, arr2, v, easingFn)
if (inertia) {
let inertiaExtra = inertiaValue * inertia
if (inertiaMode === 'absolute') inertiaExtra = Math.abs((inertiaExtra))
interpolatedValue += inertiaExtra
}
const unit = cssUnit || pxUnits.includes(key) ? 'px' : (degUnits.includes(key) ? 'deg' : '')
const dp = unit === 'px' ? 0 : 3
const val = interpolatedValue.toFixed(dp)
styles[key] = cssFn ? cssFn(val, this.domElement) : val + cssUnit
}
}
}
this.applyStyles(styles)
if (this.onUpdate) this.onUpdate(driverValues, this.domElement)
}
calculateTransforms = () => {
this.transforms = {}
const windowWidth = this.laxInstance.windowWidth
for (let driverName in this.transformsData) {
let styleBindings = this.transformsData[driverName]
const parsedStyleBindings = {}
const { presets = [] } = styleBindings
presets.forEach((presetString) => {
const [presetName, y, str] = presetString.split(":")
const presetFn = window.lax.presets[presetName]
if (!presetFn) {
console.error("Lax preset cannot be found with name: ", presetName)
} else {
const preset = presetFn(y, str)
Object.keys(preset).forEach((key) => {
styleBindings[key] = preset[key]
})
}
})
delete styleBindings.presets
for (let key in styleBindings) {
let [arr1 = [-1e9, 1e9], arr2 = [-1e9, 1e9], options = {}] = styleBindings[key]
const saveTransform = this.domElement.style.transform
this.domElement.style.removeProperty("transform")
const bounds = this.domElement.getBoundingClientRect()
this.domElement.style.transform = saveTransform
const parsedArr1 = getArrayValues(arr1, windowWidth).map(i => parseValue(i, bounds, this.groupIndex))
const parsedArr2 = getArrayValues(arr2, windowWidth).map(i => parseValue(i, bounds, this.groupIndex))
parsedStyleBindings[key] = [parsedArr1, parsedArr2, options]
}
this.transforms[driverName] = parsedStyleBindings
}
}
applyStyles = (styles) => {
const mergedStyles = flattenStyles(styles)
Object.keys(mergedStyles).forEach((key) => {
this.domElement.style.setProperty(key, mergedStyles[key])
})
}
}
class Lax {
drivers = []
elements = []
frame = 0
debug = false
windowWidth = 0
windowHeight = 0
presets = laxPresets
debugData = {
frameLengths: []
}
init = () => {
this.findAndAddElements()
window.requestAnimationFrame(this.onAnimationFrame)
this.windowWidth = document.body.clientWidth
this.windowHeight = document.body.clientHeight
window.onresize = this.onWindowResize
}
onWindowResize = () => {
const changed = document.body.clientWidth !== this.windowWidth ||
document.body.clientHeight !== this.windowHeight
if (changed) {
this.windowWidth = document.body.clientWidth
this.windowHeight = document.body.clientHeight
this.elements.forEach(el => el.calculateTransforms())
}
}
onAnimationFrame = (e) => {
if (this.debug) {
this.debugData.frameStart = Date.now()
}
const driverValues = {}
this.drivers.forEach((driver) => {
driverValues[driver.name] = driver.getValue(this.frame)
})
this.elements.forEach((element) => {
element.update(driverValues, this.frame)
})
if (this.debug) {
this.debugData.frameLengths.push(Date.now() - this.debugData.frameStart)
}
if (this.frame % 60 === 0 && this.debug) {
const averageFrameTime = Math.ceil((this.debugData.frameLengths.reduce((a, b) => a + b, 0) / 60))
console.log(`Average frame calculation time: ${averageFrameTime}ms`)
this.debugData.frameLengths = []
}
this.frame++
window.requestAnimationFrame(this.onAnimationFrame)
}
addDriver = (name, getValueFn, options = {}) => {
this.drivers.push(
new LaxDriver(name, getValueFn, options)
)
}
removeDriver = (name) => {
this.drivers = this.drivers.filter(driver => driver.name !== name)
}
findAndAddElements = () => {
this.elements = []
const elements = document.querySelectorAll(".lax")
elements.forEach((domElement) => {
const driverName = "scrollY"
const presets = []
domElement.classList.forEach((className) => {
if (className.includes("lax_preset")) {
const preset = className.replace("lax_preset_", "")
presets.push(preset)
}
})
const transforms = {
[driverName]: {
presets
}
}
this.elements.push(new LaxElement('.lax', this, domElement, transforms, 0, {}))
})
}
addElements = (selector, transforms, options) => {
const domElements = options.domElements || document.querySelectorAll(selector)
domElements.forEach((domElement, i) => {
this.elements.push(new LaxElement(selector, this, domElement, transforms, i, options))
})
}
removeElements = (selector) => {
this.elements = this.elements.filter(element => element.selector !== selector)
}
addElement = (domElement, transforms, options) => {
this.elements.push(new LaxElement('', this, domElement, transforms, 0, options))
}
removeElement = (domElement) => {
this.elements = this.elements.filter(element => element.domElement !== domElement)
}
}
return new Lax()
})()
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined')
module.exports = laxInstance
else
window.lax = laxInstance
})()

666
src/lax.ts Normal file
View File

@@ -0,0 +1,666 @@
import { DriverOptions, LaxPresetFn, ElementOptions, DriverTransforms, LaxPresetName, LaxStyleProps, LaxStyleMap, LaxPresetStyleProps } from './types'
const isPresetName = (presetName: string): presetName is LaxPresetName => [
"fadeIn",
"fadeOut",
"fadeInOut",
"scaleIn",
"scaleOut",
"scaleInOut",
"slideX",
"slideY",
"jiggle",
"seesaw",
"zigzag",
"hueRotate",
"spin",
"flipX",
"flipY",
"blurIn",
"blurOut",
"blurInOut"
].indexOf(presetName) !== -1 ? true : false
const inOutMap = (y: number | string = 30) => {
return ["elInY+elHeight", `elCenterY-${y}`, "elCenterY", `elCenterY+${y}`, "elOutY-elHeight"]
}
const laxPresets: { [key in LaxPresetName]: LaxPresetFn } = {
fadeInOut: (y = 30, str = 0) => ({
"opacity": [
inOutMap(y),
[str, 1, 1, 1, str]
],
}),
fadeIn: (y = 'elCenterY', str = 0) => ({
"opacity": [
["elInY+elHeight", y],
[str, 1],
],
}),
fadeOut: (y = 'elCenterY', str = 0) => ({
"opacity": [
[y, "elOutY-elHeight"],
[1, str],
],
}),
blurInOut: (y = 100, str = 20) => ({
"blur": [
inOutMap(y),
[str, 0, 0, 0, str],
],
}),
blurIn: (y = 'elCenterY', str = 20) => ({
"blur": [
["elInY+elHeight", y],
[str, 0],
],
}),
blurOut: (y = 'elCenterY', str = 20) => ({
"opacity": [
[y, "elOutY-elHeight"],
[0, str],
],
}),
scaleInOut: (y = 100, str = 0.6) => ({
"scale": [
inOutMap(y),
[str, 1, 1, 1, str],
],
}),
scaleIn: (y = 'elCenterY', str = 0.6) => ({
"scale": [
["elInY+elHeight", y],
[str, 1],
],
}),
scaleOut: (y = 'elCenterY', str = 0.6) => ({
"scale": [
[y, "elOutY-elHeight"],
[1, str],
],
}),
slideX: (y = 0, str = 500) => ({
"translateX": [
['elInY', y],
[0, str],
],
}),
slideY: (y = 0, str = 500) => ({
"translateY": [
['elInY', y],
[0, str],
],
}),
spin: (y: number = 1000, str: number = 360) => ({
"rotate": [
[0, y],
[0, str],
{
modValue: y,
}
],
}),
flipX: (y: number = 1000, str: number = 360) => ({
"rotateX": [
[0, y],
[0, str],
{
modValue: y
}
],
}),
flipY: (y: number = 1000, str: number = 360) => ({
"rotateY": [
[0, y],
[0, str],
{
modValue: y
}
],
}),
jiggle: (y: number = 50, str: number = 40) => ({
"skewX": [
[0, y * 1, y * 2, y * 3, y * 4],
[0, str, 0, -str, 0],
{
modValue: y * 4,
}
],
}),
seesaw: (y: number = 50, str: number = 40) => ({
"skewY": [
[0, y * 1, y * 2, y * 3, y * 4],
[0, str, 0, -str, 0],
{
modValue: y * 4,
}
],
}),
zigzag: (y: number = 100, str: number = 100) => ({
"translateX": [
[0, y * 1, y * 2, y * 3, y * 4],
[0, str, 0, -str, 0],
{
modValue: y * 4,
}
],
}),
hueRotate: (y: number = 600, str: number = 360) => ({
"hue-rotate": [
[0, y],
[0, str],
{
modValue: y,
}
],
}),
}
const transformKeys = ["perspective", "scaleX", "scaleY", "scale", "skewX", "skewY", "skew", "rotateX", "rotateY", "rotate"]
const filterKeys = ["blur", "hue-rotate", "brightness"]
const translate3dKeys = ["translateX", "translateY", "translateZ"]
const pxUnits = ["perspective", "border-radius", "blur", "translateX", "translateY", "translateZ"]
const degUnits = ["hue-rotate", "rotate", "rotateX", "rotateY", "skew", "skewX", "skewY"]
function getArrayValues(arr: Array<number>, windowWidth: number) {
if (Array.isArray(arr)) return arr
const keys = Object.keys(arr).map(x => parseInt(x)).sort((a, b) => a > b ? 1 : -1)
let retKey = keys[keys.length - 1]
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if (windowWidth < key) {
retKey = key
break
}
}
return arr[retKey]
}
function lerp(start: number, end: number, t: number) {
return start * (1 - t) + end * t
}
function invlerp(a: number, b: number, v: number) {
return (v - a) / (b - a)
}
function interpolate(arrA: number[], arrB: number[], v: number, easingFn: (vector: any) => any) {
let k = 0
arrA.forEach((a: number) => {
if (a < v) k++
})
if (k <= 0) {
return arrB[0]
}
if (k >= arrA.length) {
return arrB[arrA.length - 1]
}
const j = k - 1
let vector = invlerp(arrA[j], arrA[k], v)
if (easingFn) vector = easingFn(vector)
const lerpVal = lerp(arrB[j], arrB[k], vector)
return lerpVal
}
const easings = {
easeInQuad: (t: number) => t * t,
easeOutQuad: (t: number) => t * (2 - t),
easeInOutQuad: (t: number) => t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
easeInCubic: (t: number) => t * t * t,
easeOutCubic: (t: number) => (--t) * t * t + 1,
easeInOutCubic: (t: number) => t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
easeInQuart: (t: number) => t * t * t * t,
easeOutQuart: (t: number) => 1 - (--t) * t * t * t,
easeInOutQuart: (t: number) => t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t,
easeInQuint: (t: number) => t * t * t * t * t,
easeOutQuint: (t: number) => 1 + (--t) * t * t * t * t,
easeInOutQuint: (t: number) => t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t,
easeOutBounce: (t: number) => {
const n1 = 7.5625
const d1 = 2.75
if (t < 1 / d1) {
return n1 * t * t
} else if (t < 2 / d1) {
return n1 * (t -= 1.5 / d1) * t + 0.75
} else if (t < 2.5 / d1) {
return n1 * (t -= 2.25 / d1) * t + 0.9375
} else {
return n1 * (t -= 2.625 / d1) * t + 0.984375
}
},
easeInBounce: (t: number) => {
return 1 - easings.easeOutBounce(1 - t)
},
easeOutBack: (t: number) => {
const c1 = 1.70158
const c3 = c1 + 1
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2)
},
easeInBack: (t: number) => {
const c1 = 1.70158
const c3 = c1 + 1
return c3 * t * t * t - c1 * t * t
},
}
function flattenStyles(styles: Partial<{ [key in (keyof LaxStyleProps) | "transform" | "filter"]: number | string }>) {
const flattenedStyles = {
transform: '',
filter: ''
}
const translate3dValues = {
translateX: 0.00001,
translateY: 0.00001,
translateZ: 0.00001
}
Object.keys(styles).forEach((key: Partial<keyof LaxStyleProps>) => {
const val = styles[key]
const unit = pxUnits.includes(key) ? 'px' : (degUnits.includes(key) ? 'deg' : '')
if (translate3dKeys.includes(key)) {
translate3dValues[<keyof typeof translate3dValues>key] = <number>val // this was very annoying to get right but I think this should work properly
} else if (transformKeys.includes(key)) {
flattenedStyles.transform += `${key}(${val}${unit}) `
} else if (filterKeys.includes(key)) {
flattenedStyles.filter += `${key}(${val}${unit}) `
} else {
flattenedStyles[<keyof typeof flattenedStyles>key] = `${val}${unit} `
}
})
flattenedStyles.transform = `translate3d(${translate3dValues.translateX}px, ${translate3dValues.translateY}px, ${translate3dValues.translateZ}px) ${flattenedStyles.transform}`
return flattenedStyles
}
// https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY#Notes
function getScrollPosition() {
const supportPageOffset = window.pageXOffset !== undefined
const isCSS1Compat = ((document.compatMode || '') === 'CSS1Compat')
const x = supportPageOffset ? window.pageXOffset : isCSS1Compat ? document.documentElement.scrollLeft : document.body.scrollLeft
const y = supportPageOffset ? window.pageYOffset : isCSS1Compat ? document.documentElement.scrollTop : document.body.scrollTop
return [y, x]
}
type boxData = { width: number, height: number, x: number, y: number }
function parseValue(val: number | string, { width, height, x, y }: boxData, index: number) {
if (typeof val === 'number') {
return val
}
if (typeof val === 'string') {
const pageHeight = document.body.scrollHeight
const pageWidth = document.body.scrollWidth
const screenWidth = window.innerWidth
const screenHeight = window.innerHeight
const [scrollTop, scrollLeft] = getScrollPosition()
const left = x + scrollLeft
const right = left + width
const top = y + scrollTop
const bottom = top + height
return Function(`return ${val
.replace(/screenWidth/g, String(screenWidth))
.replace(/screenHeight/g, String(screenHeight))
.replace(/pageHeight/g, String(pageHeight))
.replace(/pageWidth/g, String(pageWidth))
.replace(/elWidth/g, String(width))
.replace(/elHeight/g, String(height))
.replace(/elInY/g, String(top - screenHeight))
.replace(/elOutY/g, String(bottom))
.replace(/elCenterY/g, String(top + (height / 2) - (screenHeight / 2)))
.replace(/elInX/g, String(left - screenWidth))
.replace(/elOutX/g, String(right))
.replace(/elCenterX/g, String(left + (width / 2) - (screenWidth / 2)))
.replace(/index/g, String(index))
}`)()
}
}
class LaxDriver {
getValueFn
name = ''
lastValue = 0
frameStep = 1
m1 = 0
m2 = 0
inertia = 0
inertiaEnabled = false
constructor(name: string, getValueFn: (frame?: number) => number, options: DriverOptions = {}) {
this.name = name
this.getValueFn = getValueFn
this.inertiaEnabled = options.inertiaEnabled
this.frameStep = options.frameStep
this.lastValue = this.getValueFn(0)
}
getValue = (frame: number) => {
let value = this.lastValue
if (frame % this.frameStep === 0) {
value = this.getValueFn(frame)
}
if (this.inertiaEnabled) {
const delta = value - this.lastValue
const damping = 0.8
this.m1 = this.m1 * damping + delta * (1 - damping)
this.m2 = this.m2 * damping + this.m1 * (1 - damping)
this.inertia = Math.round(this.m2 * 5000) / 15000
}
this.lastValue = value
return [this.lastValue, this.inertia]
}
}
class LaxElement {
domElement
transformsData
styles = {}
selector = ''
groupIndex = 0
laxInstance
onUpdate
transforms: DriverTransforms
constructor(selector: string, laxInstance: Lax, domElement: HTMLElement, transformsData: DriverTransforms, groupIndex = 0, options: ElementOptions = {}) {
this.selector = selector
this.laxInstance = laxInstance
this.domElement = domElement
this.transformsData = transformsData
this.groupIndex = groupIndex
const { style = {}, onUpdate } = options
Object.keys(style).forEach(key => {
domElement.style.setProperty(key, style[key])
})
if (onUpdate) this.onUpdate = onUpdate
this.calculateTransforms()
}
update = (driverValues: { [key: string]: Array<number> }, frame: number) => {
const { transforms } = this
const styles: { [key in keyof LaxStyleProps]: string | number } = {}
for (let driverName in transforms) {
const styleBindings = transforms[driverName]
if (!driverValues[driverName]) {
console.error("No lax driver with name: ", driverName)
}
const [value, inertiaValue] = driverValues[driverName]
let key: keyof LaxStyleProps
for (key in styleBindings) {
if (key !== "presets") {
const [arr1, arr2, options = {}] = styleBindings[key]
const { modValue, frameStep = 1, easing, inertia, inertiaMode, cssFn, cssUnit = '' } = options
const easingFn = easings[easing]
if (frame % frameStep === 0) {
const v = modValue ? value % modValue : value
let interpolatedValue = interpolate(<number[]>arr1, <number[]>arr2, v, easingFn) // because this function is only used internally, this casting is valid
if (inertia) {
let inertiaExtra = inertiaValue * inertia
if (inertiaMode === 'absolute') inertiaExtra = Math.abs((inertiaExtra))
interpolatedValue += inertiaExtra
}
const unit = cssUnit || pxUnits.includes(key) ? 'px' : (degUnits.includes(key) ? 'deg' : '')
const dp = unit === 'px' ? 0 : 3
const val = unit === 'px' ? Number.parseFloat(interpolatedValue.toFixed(dp)) : interpolatedValue
styles[key] = cssFn ? cssFn(val, this.domElement) : val + cssUnit
}
}
}
}
this.applyStyles(styles)
if (this.onUpdate) this.onUpdate(driverValues, this.domElement)
}
calculateTransforms = () => {
this.transforms = {}
const windowWidth = this.laxInstance.windowWidth
let driverName: keyof DriverTransforms
for (driverName in this.transformsData) {
let styleBindings: LaxStyleProps = this.transformsData[driverName]
const parsedStyleBindings: { [key in keyof LaxStyleProps]: LaxStyleMap } = {}
const { presets = <Array<string>>[] } = styleBindings
presets.forEach((presetString) => {
const [presetName, y, str] = presetString.split(":")
const presetFn: LaxPresetFn = window["lax"].presets[<keyof LaxPresetFn>presetName]
if (!presetFn) {
console.error("Lax preset cannot be found with name: ", presetName)
} else {
const preset = presetFn(y, str)
Object.keys(preset).forEach((key: keyof LaxPresetStyleProps) => {
styleBindings[key] = preset[key]
})
}
})
delete styleBindings.presets
let key: keyof LaxStyleProps
for (key in styleBindings) {
if (key !== "presets") { // should always be true in here, but typescript wants to be 100% sure
let [arr1 = [-1e9, 1e9], arr2 = [-1e9, 1e9], options = {}] = styleBindings[key]
const saveTransform = this.domElement.style.transform
this.domElement.style.removeProperty("transform")
const bounds = this.domElement.getBoundingClientRect()
this.domElement.style.transform = saveTransform
const parsedArr1 = getArrayValues(<Array<number>>arr1, windowWidth).map(i => parseValue(i, bounds, this.groupIndex)) // should always work here
const parsedArr2 = getArrayValues(<Array<number>>arr2, windowWidth).map(i => parseValue(i, bounds, this.groupIndex)) // should always work here
parsedStyleBindings[key] = [parsedArr1, parsedArr2, options]
}
}
this.transforms[driverName] = parsedStyleBindings
}
}
applyStyles = (styles: { [key in keyof LaxStyleProps]: number | string }) => {
const mergedStyles = flattenStyles(styles)
Object.keys(mergedStyles).forEach((key: "transform" | "filter") => {
this.domElement.style.setProperty(key, mergedStyles[key])
})
}
}
class Lax {
private drivers = <LaxDriver[]>[]
private elements: Array<LaxElement> = []
private frame = 0
private debug = false
windowWidth = 0
windowHeight = 0
presets = laxPresets
private debugData = {
frameLengths: <Array<number>>[],
frameStart: 0
}
init = () => {
this.findAndAddElements()
window.requestAnimationFrame(this.onAnimationFrame)
this.windowWidth = document.body.clientWidth
this.windowHeight = document.body.clientHeight
window.onresize = this.onWindowResize
}
onWindowResize = () => {
const changed = document.body.clientWidth !== this.windowWidth ||
document.body.clientHeight !== this.windowHeight
if (changed) {
this.windowWidth = document.body.clientWidth
this.windowHeight = document.body.clientHeight
this.elements.forEach(el => el.calculateTransforms())
}
}
onAnimationFrame = (e: DOMHighResTimeStamp) => {
if (this.debug) {
this.debugData["frameStart"] = Date.now()
}
const driverValues: { [key: string]: Array<number> } = {}
this.drivers.forEach((driver) => {
driverValues[driver.name] = driver.getValue(this.frame)
})
this.elements.forEach((element) => {
element.update(driverValues, this.frame)
})
if (this.debug) {
this.debugData.frameLengths.push(Date.now() - this.debugData["frameStart"])
}
if (this.frame % 60 === 0 && this.debug) {
const averageFrameTime = Math.ceil((this.debugData.frameLengths.reduce((a, b) => a + b, 0) / 60))
console.log(`Average frame calculation time: ${averageFrameTime}ms`)
this.debugData.frameLengths = []
}
this.frame++
window.requestAnimationFrame(this.onAnimationFrame)
}
addDriver = (name: string, getValueFn: () => number, options: DriverOptions = {}) => {
this.drivers.push(
new LaxDriver(name, getValueFn, options)
)
}
removeDriver = (name: string) => {
this.drivers = this.drivers.filter(driver => driver.name !== name)
}
findAndAddElements = () => {
this.elements = []
const elements = document.querySelectorAll(".lax")
elements.forEach((domElement: HTMLElement | Element) => {
const driverName = "scrollY"
const presets: Array<LaxPresetName> = []
domElement.classList.forEach((className) => {
if (className.includes("lax_preset")) {
const preset = className.replace("lax_preset_", "")
if (isPresetName(preset))
presets.push(preset)
}
})
const transforms = {
[driverName]: {
presets
}
}
this.elements.push(new LaxElement('.lax', this, <HTMLElement>domElement, transforms, 0))
})
}
addElements = (selector: string, transforms: DriverTransforms, options?: ElementOptions) => {
const domElements = options.domElements || document.querySelectorAll(selector)
domElements.forEach((domElement, i) => {
this.elements.push(new LaxElement(selector, this, <HTMLElement>domElement, transforms, i, options))
})
}
removeElements = (selector: string) => {
this.elements = this.elements.filter(element => element.selector !== selector)
}
addElement = (domElement: HTMLElement | Element, transforms: DriverTransforms, options?: ElementOptions) => {
this.elements.push(new LaxElement('', this, <HTMLElement>domElement, transforms, 0, options))
}
removeElement = (domElement: HTMLElement | Element) => {
this.elements = this.elements.filter(element => element.domElement !== domElement)
}
}
export const laxInstance = new Lax()
const lax = {
addElements: laxInstance.addElements,
removeElements: laxInstance.removeElements,
removeElement: laxInstance.removeElement,
addElement: laxInstance.addElement,
init: laxInstance.init,
addDriver: laxInstance.addDriver,
removeDriver: laxInstance.removeDriver,
presets: laxInstance.presets,
}
// needed because window, and global don't have lax on them initially
declare global {
namespace NodeJS {
interface Global {
lax: typeof lax
}
}
interface Window {
lax: typeof lax
}
};
(() => {
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined')
module.exports = lax
else if (window)
window.lax = lax
else if (global)
global["lax"] = lax
})()
export default lax
export const { addDriver, addElement, addElements, removeDriver, removeElement, removeElements, init, presets } = lax

130
src/types.ts Normal file
View File

@@ -0,0 +1,130 @@
export interface DriverOptions {
inertiaEnabled?: boolean
frameStep?: number
}
export interface StyleObject {
[key: string]: any
}
export type LaxPresetName =
"fadeIn" |
"fadeOut" |
"fadeInOut" |
"scaleIn" |
"scaleOut" |
"scaleInOut" |
"slideX" |
"slideY" |
"jiggle" |
"seesaw" |
"zigzag" |
"hueRotate" |
"spin" |
"flipX" |
"flipY" |
"blurIn" |
"blurOut" |
"blurInOut"
export type LaxPresetFn = (x: number | string, y: number | string) => LaxPresetStyleProps
export type easingOptions =
"easeInQuad" |
"easeOutQuad" |
"easeInOutQuad" |
"easeInCubic" |
"easeOutCubic" |
"easeInOutCubic" |
"easeInQuart" |
"easeOutQuart" |
"easeInOutQuart" |
"easeInQuint" |
"easeOutQuint" |
"easeInOutQuint" |
"easeOutBounce" |
"easeInBounce" |
"easeOutBack" |
"easeInBack"
export type specialValues =
"screenWidth" |
"screenHeight" |
"pageWidth" |
"pageHeight" |
"elWidth" |
"elHeight" |
"elInY" |
"elOutY" |
"elCenterY" |
"elInX" |
"elOutX" |
"elCenterX" |
"index"
export enum cssValues {
"opacity" = `opacity`,
"scaleX" = "scaleX",
"scaleY" = "scaleY",
"scale" = "scale",
"skewX" = "skewX",
"skewY" = "skewY",
"skew" = "skew",
"rotateX" = "rotateX",
"rotateY" = "rotateY",
"rotate" = "rotate",
"translateX" = `translateX`,
"translateY" = "translateY",
"translateZ" = "translateZ",
"blur" = "blur",
"hue-rotate" = "hue-rotate",
"brightness" = "brightness"
}
export interface LaxStyleMapOptions {
modValue?: number | undefined
frameStep?: number
inertia?: number
inertiaMode?: "normal" | "absolute"
cssUnit?: string
cssFn?(value: number, domElement: HTMLElement | Element): number | string
easing?: easingOptions
}
export type LaxStyleMap = [
Array<number | specialValues | string>,
Array<number | specialValues | string>,
LaxStyleMapOptions?
]
export interface LaxStyleProps extends LaxPresetStyleProps {
"presets"?: Array<LaxPresetName>
}
export interface LaxPresetStyleProps {
"opacity"?: LaxStyleMap
"scaleX"?: LaxStyleMap
"scaleY"?: LaxStyleMap
"scale"?: LaxStyleMap
"skewX"?: LaxStyleMap
"skewY"?: LaxStyleMap
"skew"?: LaxStyleMap
"rotateX"?: LaxStyleMap
"rotateY"?: LaxStyleMap
"rotate"?: LaxStyleMap
"translateX"?: LaxStyleMap
"translateY"?: LaxStyleMap
"translateZ"?: LaxStyleMap
"blur"?: LaxStyleMap
"hue-rotate"?: LaxStyleMap
"brightness"?: LaxStyleMap
}
export interface ElementOptions {
style?: StyleObject
domElements?: Array<HTMLElement | Element>
onUpdate?(driverValues: any, domElement: HTMLElement | Element): void
}
type DriverName = string
export interface DriverTransforms {
[key: string]: LaxStyleProps | LaxPresetStyleProps
}

20
tsconfig.json Normal file
View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES5",
"lib": ["es5", "es6", "es2017", "dom"],
"module": "CommonJS",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"strict": false,
"composite": true,
"noImplicitAny": true,
"skipLibCheck": true,
"esModuleInterop": true,
"rootDir": "src",
"outDir": "lib"
},
"include": [
"./src/**/*"
]
}

1257
yarn.lock

File diff suppressed because it is too large Load Diff