Repository: alexfoxy/lax.js Branch: dev Commit: 5d6d4c37727d Files: 20 Total size: 97.0 KB Directory structure: gitextract_2no0vpdt/ ├── .babelrc ├── .github/ │ └── ISSUE_TEMPLATE/ │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── docs/ │ ├── examples/ │ │ ├── cursor.html │ │ ├── html-inline.html │ │ ├── inertia.html │ │ ├── input.html │ │ ├── on-update.html │ │ ├── scroll.html │ │ ├── snap-scroll.html │ │ └── sprite.html │ ├── index.html │ └── preset-explorer.html ├── jsconfig.json ├── lib/ │ └── lax.js ├── package.json └── src/ └── lax.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ "@babel/preset-env" ], "plugins": [ "@babel/plugin-proposal-class-properties" ] } ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **lax.js version** Please note, only bugs in v2.0 or later will be fixed. **Describe the bug** A clear and concise description of what the bug is. **Code** Please provide a link to a repo or code example of the issue in question. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .gitignore ================================================ node_modules/ .DS_Store LaxLogo.sketch ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Alex Fox Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 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. ================================================ FILE: README.md ================================================ # Archive Notice Due to other commitments I am unable to continue maintaining this project. As far as I know it still works, but there are likely better, more up to date alterantives out there. # lax.js Simple & lightweight (<4kb gzipped) vanilla JavaScript library to create smooth & beautiful animations when you scroll. ![Lax 2.0 Gif](https://i.imgur.com/XNvvAOv.gif) [>> DEMO <<](https://alexfox.dev/lax.js/) --- ## What's new with Lax.js 2.0 Lax.js 2.0 has been completely re-written with a focus on modularity and flexibility giving you more tools to create awesome animations. - New javascript animation syntax, allowing for more advanced effect combos - Use any value to drive animations, for example mouse position, time of day .. and of course scroll! - Animations can be given inertia when scrolling - Create custom CSS bindings - Animation easings - And much more.. ## Examples - [Scroll effect](https://alexfox.dev/lax.js/examples/scroll) - [Horizontal snap scroll](https://alexfox.dev/lax.js/examples/snap-scroll) - [Inertia](https://alexfox.dev/lax.js/examples/inertia) - [Video/Gif playback](https://alexfox.dev/lax.js/examples/sprite) - [Cursor position](https://alexfox.dev/lax.js/examples/cursor) - [Text input](https://alexfox.dev/lax.js/examples/input) - [Update HTML content](https://alexfox.dev/lax.js/examples/on-update) - [Preset Explorer](https://alexfox.dev/lax.js/preset-explorer) # Documentation ### 1. Getting started - [Setup](#setup) - [Using presets](#using-presets) - [Usage with UI frameworks](#dom-behavior-and-usage-with-frameworks) - [Adding drivers](#adding-drivers) - [Adding elements](#adding-elements) ### 2. Going deeper - [Custom animations](#custom-animations) - [Optimising performance](#optimising-performance) ### 3. Glossary - [CSS properties](#css-properties) - [Special values](#special-values) - [Supported easings](#supported-easings) # Getting started ### NPM Setup ```bash # https://www.npmjs.com/package/lax.js npm install lax.js yarn add lax.js ``` ```js import lax from 'lax.js' ``` ### HTML setup ```html ``` ## Setup To implement lax you need to create at least one _driver_, to provide values for animations, as well as the element animation bindings. Below is a simple example: ```html
Hello
``` ## Using presets The easiest way to get started is to use presets via html classes. For example: ```html
``` Multiple presets can be chained together and they can be customised to suit your needs. Use the [preset explorer](https://alexfox.dev/lax.js/preset-explorer) to explore effects and see a simple example [here](https://alexfox.dev/lax.js/examples/html-inline). ## DOM behavior and usage with Frameworks To increase performance, `lax.js` indexes the list of elements to animate when the page loads. If you're using a library like React, Vue or EmberJS, it is likely that you are adding elements after the initial window.onload. Because of this you will need to call `lax.addElements` when you add components to the DOM that you want to animate, and `lax.removeElements` when the component unmounts. Please find a React example [here](https://codesandbox.io/s/laxjs-react-example-nc4h7). Other examples will be available soon for Vue.js and Angular. ## Adding drivers Drivers provide the values that _drive_ your animations. To set up a driver just call `lax.addDriver` with a name and a function which returns a number. This method is called every frame to calculate the animations so keep the method as computationally _light_ as possible. The example below will be the most common use case for lax which returns the scrollY position of the window. ```javascript lax.addDriver( 'scrollY', // Driver name function(laxFrame) { return window.scrollY // Value method }, { } // Options ) ``` ### Driver options #### `inertiaEnabled: boolean = false` If enabled, the driver will calculate the speed at which its value is changing. Used to add inertia to elements using the [inertia element option](#inertia-number). See this in action in the [here](https://alexfox.dev/lax.js/examples/inertia). #### `frameStep: number = 1` By default each driver updates its value every animation frame, around ~60 times per second. You can use the `frameStep` to reduce frequency of the driver value updating. For example a value of `2` would only update ~30 times per second and a value of `60` would only update about once per second. ## Adding elements You can add lax animations to an element using the `addElements` method: ```javascript lax.addElements( '.selector', // Element selector rule { // Animation data scrollY: { opacity: [ [0, 100], [1, 0] ] } }, { style: {} // Element options } ) ``` ### Element options #### `style: StyleObject` Add static CSS to each element, for example: ```css { transform: '200ms scale ease-in-out'; } ``` #### `elements: Array` Pass references to raw DOM elements to allow for more flexible selection patterns. In this case, a unique `selector` must still be passed as the first argument, however it does _not_ need to be a valid DOM selector. This allows the library to tag the elements for removal later. Example: ```js const myDomElements = $('.selector') { elements: myDomElements } ``` #### `onUpdate: (driverValues: Object, domElement: DomElement) => void` A method called every frame with the current driverValues and domElement. This could be used to toggle classes on an element or set innerHTML. See it in action [here](https://alexfox.dev/lax.js/examples/on-update). The driver values are formatted as follows: ```js { scrollY: [ // Driver name 100, // Driver value 0 // Driver inertia ] } ``` # Going deeper ## Custom animations Custom animations are defined using an object. ```javascript // Animation data { scrollY: { // Driver name translateX: [ // CSS property ['elInY', 'elOutY'], // Driver value map [0, 'screenWidth'], // Animation value map { inertia: 10 // Options } ], opacity: [ // etc ] } } ``` ### Driver name The name of the driver you want to use as a source of values to map to your animation, for example, the document's scrollY position. Read about adding drivers [here](#adding-drivers). ### CSS property The name of the CSS property you want to animate, for example `opacity` or `rotate`. See a list of supported properties [here](#css-properties). > Some CSS properties, for example `box-shadow`, require a custom function to build the style string. To do this use the [cssFn](#cssfn-value-number--string) element option. ### Value maps The value maps are used to interpolate the driver value and output a value for your CSS property. For example: ```javascript [0, 200, 800] // Driver value map [0, 10, 20] // Animation value map // Result | In | Out | | --- | --- | | 0 | 0 | | 100 | 5 | | 200 | 10 | | 500 | 15 | | 800 | 20 | ``` Within the maps you can use strings for simple formulas as well as use special values. e.g: ```javascript ['elInY', 'elCenterY-200', 'elCenterY', ``` See a list of available values [here](#special-values). You can also use mobile breakpoints within driver value maps and animation maps for more flexibility. ```javascript scrollY: { translateX: [ ['elInY', 'elCenterY', 'elOutY'], { 500: [10, 20, 50], // Screen width < 500 900: [30, 40, 60], // Screen width > 500 and < 900 1400: [30, 40, 60], // Screen width > 900 }, ]; } ``` ### Options #### `modValue: number | undefined` Set this option to modulus the value from the driver, for example if you want to loop the animation value as the driver value continues to increase. #### `frameStep: number = 1` By default each animation updates its value every animation frame, around ~60 times per second. You can use the `frameStep` to reduce frequency of the animation updating. For example a value of `2` would only update ~30 times per second and a value of `60` would only update about once per second. #### `inertia: number` Use to add inertia to your animations. Use in combination with the [inertiaEnabled](#inertiaenabled-boolean--false) driver option. See inertia in action [here](https://alexfox.dev/lax.js/examples/inertia). #### `inertiaMode: "normal" | "absolute"` Use in combination with `inertia`. If set to `absolute` the inertia value will always be a positive number via the `Math.abs` operator. #### `cssUnit: string = ""` Define the unit to be appended to the end of the value, for example For example `px` `deg` #### `cssFn: (value: number, domElement: DomElement) => number | string` Some CSS properties require more complex strings as values. For example, `box-shadow` has multiple values that could be modified by a lax animation. ```javascript // Box-shadow example (val) => { return `${val}px ${val}px ${val}px rgba(0,0,0,0.5)`; }; ``` #### `easing: string` See a list of available values [here](#supported-easings). ## Optimising performance Lax.js has been designed to be performant but there are a few things to bare in mind when creating your websites. - Smaller elements perform better. - Postion `fixed` and `absolute` elements perform best as they do not trigger a layout change when updated. - Off-screen elements do not need to be updated so consider that when creating your animation value maps. - The css properties `blur`, `hue-rotate` and `brightness` are graphically intensive and do not run as smoothly as the other available properties. # Glossary ## CSS properties | name | | ---------- | | opacity | | scaleX | | scaleY | | scale | | skewX | | skewY | | skew | | rotateX | | rotateY | | rotate | | translateX | | translateY | | translateZ | | blur | | hue-rotate | | brightness | ## Special values | key | value | | ------------ | -------------------------------------------------------------------------------- | | screenWidth | current width of the screen | | screenHeight | current height of the screen | | pageWidth | width of the document | | pageHeight | height of the document | | elWidth | width of the element | | elHeight | height of the element | | elInY | window scrollY position when element will appear at the bottom of the screen | | elOutY | window scrollY position when element will disappear at the top of the screen | | elCenterY | window scrollY position when element will be centered vertically on the screen | | elInX | window scrollX position when element will appear at the right of the screen | | elOutX | window scrollX position when element will disappear at the left of the screen | | elCenterX | window scrollX position when element will be centered horizontally on the screen | | index | index of the element when added using `lax.addElements` | ## Supported easings | name | | -------------- | | easeInQuad | | easeOutQuad | | easeInOutQuad | | easeInCubic | | easeOutCubic | | easeInOutCubic | | easeInQuart | | easeOutQuart | | easeInOutQuart | | easeInQuint | | easeOutQuint | | easeInOutQuint | | easeOutBounce | | easeInBounce | | easeOutBack | | easeInBack | ================================================ FILE: docs/examples/cursor.html ================================================

Lax.js

Lax.js

Lax.js

Lax.js

Lax.js

Lax.js

Lax.js

Lax.js

Lax.js

Lax.js

Lax.js

Lax.js

Lax.js

Lax.js

Lax.js

Lax.js

Lax.js

Lax.js

Lax.js

Lax.js

================================================ FILE: docs/examples/html-inline.html ================================================
================================================ FILE: docs/examples/inertia.html ================================================
================================================ FILE: docs/examples/input.html ================================================ ================================================ FILE: docs/examples/on-update.html ================================================
================================================ FILE: docs/examples/scroll.html ================================================
================================================ FILE: docs/examples/snap-scroll.html ================================================

Superstar

Classics never go out of style. An instant icon since their debut, adidas Superstar Shoes first rose to fame on the basketball courts of the '70s and haven't slowed their roll since.

Retrorun

Retro design with a modern twist. Check out a busy side street or stroll to the corner store in these adidas running-inspired shoes. Suede overlays and contrast 3-Stripes give the flexible upper a sleek, sporty finish.

Grand Court

A '70s style reborn. These shoes take inspiration from iconic sport styles of the past and move them into the future. The shoes craft an everyday look with a leather-like upper.

================================================ FILE: docs/examples/sprite.html ================================================
================================================ FILE: docs/index.html ================================================

scroll down

oooh

aaah

wheee!

Get lax.js
================================================ FILE: docs/preset-explorer.html ================================================

Lax preset explorer

Preset code How to use

Scroll Down..

Scroll Up..

================================================ FILE: jsconfig.json ================================================ { "compilerOptions": { "checkJs": true }, } ================================================ FILE: lib/lax.js ================================================ "use strict"; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } (function () { var inOutMap = function inOutMap() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 30; return ["elInY+elHeight", "elCenterY-".concat(y), "elCenterY", "elCenterY+".concat(y), "elOutY-elHeight"]; }; var laxPresets = { fadeInOut: function fadeInOut() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 30; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; return { "opacity": [inOutMap(y), [str, 1, 1, 1, str]] }; }, fadeIn: function fadeIn() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'elCenterY'; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; return { "opacity": [["elInY+elHeight", y], [str, 1]] }; }, fadeOut: function fadeOut() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'elCenterY'; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; return { "opacity": [[y, "elOutY-elHeight"], [1, str]] }; }, blurInOut: function blurInOut() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 100; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 20; return { "blur": [inOutMap(y), [str, 0, 0, 0, str]] }; }, blurIn: function blurIn() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'elCenterY'; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 20; return { "blur": [["elInY+elHeight", y], [str, 0]] }; }, blurOut: function blurOut() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'elCenterY'; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 20; return { "opacity": [[y, "elOutY-elHeight"], [0, str]] }; }, scaleInOut: function scaleInOut() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 100; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0.6; return { "scale": [inOutMap(y), [str, 1, 1, 1, str]] }; }, scaleIn: function scaleIn() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'elCenterY'; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0.6; return { "scale": [["elInY+elHeight", y], [str, 1]] }; }, scaleOut: function scaleOut() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'elCenterY'; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0.6; return { "scale": [[y, "elOutY-elHeight"], [1, str]] }; }, slideX: function slideX() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 500; return { "translateX": [['elInY', y], [0, str]] }; }, slideY: function slideY() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 500; return { "translateY": [['elInY', y], [0, str]] }; }, spin: function spin() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1000; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 360; return { "rotate": [[0, y], [0, str], { modValue: y }] }; }, flipX: function flipX() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1000; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 360; return { "rotateX": [[0, y], [0, str], { modValue: y }] }; }, flipY: function flipY() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1000; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 360; return { "rotateY": [[0, y], [0, str], { modValue: y }] }; }, jiggle: function jiggle() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 50; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 40; return { "skewX": [[0, y * 1, y * 2, y * 3, y * 4], [0, str, 0, -str, 0], { modValue: y * 4 }] }; }, seesaw: function seesaw() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 50; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 40; return { "skewY": [[0, y * 1, y * 2, y * 3, y * 4], [0, str, 0, -str, 0], { modValue: y * 4 }] }; }, zigzag: function zigzag() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 100; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 100; return { "translateX": [[0, y * 1, y * 2, y * 3, y * 4], [0, str, 0, -str, 0], { modValue: y * 4 }] }; }, hueRotate: function hueRotate() { var y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 600; var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 360; return { "hue-rotate": [[0, y], [0, str], { modValue: y }] }; } }; var laxInstance = function () { var transformKeys = ["perspective", "scaleX", "scaleY", "scale", "skewX", "skewY", "skew", "rotateX", "rotateY", "rotate"]; var filterKeys = ["blur", "hue-rotate", "brightness"]; var translate3dKeys = ["translateX", "translateY", "translateZ"]; var pxUnits = ["perspective", "border-radius", "blur", "translateX", "translateY", "translateZ"]; var degUnits = ["hue-rotate", "rotate", "rotateX", "rotateY", "skew", "skewX", "skewY"]; function getArrayValues(arr, windowWidth) { if (Array.isArray(arr)) return arr; var keys = Object.keys(arr).map(function (x) { return parseInt(x); }).sort(function (a, b) { return a > b ? 1 : -1; }); var retKey = keys[keys.length - 1]; for (var i = 0; i < keys.length; i++) { var 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) { var k = 0; arrA.forEach(function (a) { if (a < v) k++; }); if (k <= 0) { return arrB[0]; } if (k >= arrA.length) { return arrB[arrA.length - 1]; } var j = k - 1; var vector = invlerp(arrA[j], arrA[k], v); if (easingFn) vector = easingFn(vector); var lerpVal = lerp(arrB[j], arrB[k], vector); return lerpVal; } var easings = { easeInQuad: function easeInQuad(t) { return t * t; }, easeOutQuad: function easeOutQuad(t) { return t * (2 - t); }, easeInOutQuad: function easeInOutQuad(t) { return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t; }, easeInCubic: function easeInCubic(t) { return t * t * t; }, easeOutCubic: function easeOutCubic(t) { return --t * t * t + 1; }, easeInOutCubic: function easeInOutCubic(t) { return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; }, easeInQuart: function easeInQuart(t) { return t * t * t * t; }, easeOutQuart: function easeOutQuart(t) { return 1 - --t * t * t * t; }, easeInOutQuart: function easeInOutQuart(t) { return t < .5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t; }, easeInQuint: function easeInQuint(t) { return t * t * t * t * t; }, easeOutQuint: function easeOutQuint(t) { return 1 + --t * t * t * t * t; }, easeInOutQuint: function easeInOutQuint(t) { return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t; }, easeOutBounce: function easeOutBounce(t) { var n1 = 7.5625; var 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: function easeInBounce(t) { return 1 - easings.easeOutBounce(1 - t); }, easeOutBack: function easeOutBack(t) { var c1 = 1.70158; var c3 = c1 + 1; return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2); }, easeInBack: function easeInBack(t) { var c1 = 1.70158; var c3 = c1 + 1; return c3 * t * t * t - c1 * t * t; } }; function flattenStyles(styles) { var flattenedStyles = { transform: '', filter: '' }; var translate3dValues = { translateX: 0.00001, translateY: 0.00001, translateZ: 0.00001 }; Object.keys(styles).forEach(function (key) { var val = styles[key]; var unit = pxUnits.includes(key) ? 'px' : degUnits.includes(key) ? 'deg' : ''; if (translate3dKeys.includes(key)) { translate3dValues[key] = val; } else if (transformKeys.includes(key)) { flattenedStyles.transform += "".concat(key, "(").concat(val).concat(unit, ") "); } else if (filterKeys.includes(key)) { flattenedStyles.filter += "".concat(key, "(").concat(val).concat(unit, ") "); } else { flattenedStyles[key] = "".concat(val).concat(unit, " "); } }); flattenedStyles.transform = "translate3d(".concat(translate3dValues.translateX, "px, ").concat(translate3dValues.translateY, "px, ").concat(translate3dValues.translateZ, "px) ").concat(flattenedStyles.transform); return flattenedStyles; } // https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY#Notes function getScrollPosition() { var supportPageOffset = window.pageXOffset !== undefined; var isCSS1Compat = (document.compatMode || '') === 'CSS1Compat'; var x = supportPageOffset ? window.pageXOffset : isCSS1Compat ? document.documentElement.scrollLeft : document.body.scrollLeft; var y = supportPageOffset ? window.pageYOffset : isCSS1Compat ? document.documentElement.scrollTop : document.body.scrollTop; return [y, x]; } function parseValue(val, _ref, index) { var width = _ref.width, height = _ref.height, x = _ref.x, y = _ref.y; if (typeof val === 'number') { return val; } var pageHeight = document.body.scrollHeight; var pageWidth = document.body.scrollWidth; var screenWidth = window.innerWidth; var screenHeight = window.innerHeight; var _getScrollPosition = getScrollPosition(), _getScrollPosition2 = _slicedToArray(_getScrollPosition, 2), scrollTop = _getScrollPosition2[0], scrollLeft = _getScrollPosition2[1]; var left = x + scrollLeft; var right = left + width; var top = y + scrollTop; var bottom = top + height; return Function("return ".concat(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)))(); } var LaxDriver = function LaxDriver(name, getValueFn) { var _this = this; var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; _classCallCheck(this, LaxDriver); _defineProperty(this, "getValueFn", void 0); _defineProperty(this, "name", ''); _defineProperty(this, "lastValue", 0); _defineProperty(this, "frameStep", 1); _defineProperty(this, "m1", 0); _defineProperty(this, "m2", 0); _defineProperty(this, "inertia", 0); _defineProperty(this, "inertiaEnabled", false); _defineProperty(this, "getValue", function (frame) { var value = _this.lastValue; if (frame % _this.frameStep === 0) { value = _this.getValueFn(frame); } if (_this.inertiaEnabled) { var delta = value - _this.lastValue; var 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]; }); this.name = name; this.getValueFn = getValueFn; Object.keys(options).forEach(function (key) { _this[key] = options[key]; }); this.lastValue = this.getValueFn(0); }; var LaxElement = function LaxElement(selector, laxInstance, domElement, transformsData) { var _this2 = this; var groupIndex = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0; var _options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {}; _classCallCheck(this, LaxElement); _defineProperty(this, "domElement", void 0); _defineProperty(this, "transformsData", void 0); _defineProperty(this, "styles", {}); _defineProperty(this, "selector", ''); _defineProperty(this, "groupIndex", 0); _defineProperty(this, "laxInstance", void 0); _defineProperty(this, "onUpdate", void 0); _defineProperty(this, "update", function (driverValues, frame) { var transforms = _this2.transforms; var styles = {}; for (var driverName in transforms) { var styleBindings = transforms[driverName]; if (!driverValues[driverName]) { console.error("No lax driver with name: ", driverName); } var _driverValues$driverN = _slicedToArray(driverValues[driverName], 2), value = _driverValues$driverN[0], inertiaValue = _driverValues$driverN[1]; for (var key in styleBindings) { var _styleBindings$key = _slicedToArray(styleBindings[key], 3), arr1 = _styleBindings$key[0], arr2 = _styleBindings$key[1], _styleBindings$key$ = _styleBindings$key[2], options = _styleBindings$key$ === void 0 ? {} : _styleBindings$key$; var modValue = options.modValue, _options$frameStep = options.frameStep, frameStep = _options$frameStep === void 0 ? 1 : _options$frameStep, easing = options.easing, inertia = options.inertia, inertiaMode = options.inertiaMode, cssFn = options.cssFn, _options$cssUnit = options.cssUnit, cssUnit = _options$cssUnit === void 0 ? '' : _options$cssUnit; var easingFn = easings[easing]; if (frame % frameStep === 0) { var v = modValue ? value % modValue : value; var interpolatedValue = interpolate(arr1, arr2, v, easingFn); if (inertia) { var inertiaExtra = inertiaValue * inertia; if (inertiaMode === 'absolute') inertiaExtra = Math.abs(inertiaExtra); interpolatedValue += inertiaExtra; } var unit = cssUnit || pxUnits.includes(key) ? 'px' : degUnits.includes(key) ? 'deg' : ''; var dp = unit === 'px' ? 0 : 3; var val = interpolatedValue.toFixed(dp); styles[key] = cssFn ? cssFn(val, _this2.domElement) : val + cssUnit; } } } _this2.applyStyles(styles); if (_this2.onUpdate) _this2.onUpdate(driverValues, _this2.domElement); }); _defineProperty(this, "calculateTransforms", function () { _this2.transforms = {}; var windowWidth = _this2.laxInstance.windowWidth; var _loop = function _loop(driverName) { var styleBindings = _this2.transformsData[driverName]; var parsedStyleBindings = {}; var _styleBindings$preset = styleBindings.presets, presets = _styleBindings$preset === void 0 ? [] : _styleBindings$preset; presets.forEach(function (presetString) { var _presetString$split = presetString.split(":"), _presetString$split2 = _slicedToArray(_presetString$split, 3), presetName = _presetString$split2[0], y = _presetString$split2[1], str = _presetString$split2[2]; var presetFn = window.lax.presets[presetName]; if (!presetFn) { console.error("Lax preset cannot be found with name: ", presetName); } else { var preset = presetFn(y, str); Object.keys(preset).forEach(function (key) { styleBindings[key] = preset[key]; }); } }); delete styleBindings.presets; var _loop2 = function _loop2(key) { var _styleBindings$key2 = _slicedToArray(styleBindings[key], 3), _styleBindings$key2$ = _styleBindings$key2[0], arr1 = _styleBindings$key2$ === void 0 ? [-1e9, 1e9] : _styleBindings$key2$, _styleBindings$key2$2 = _styleBindings$key2[1], arr2 = _styleBindings$key2$2 === void 0 ? [-1e9, 1e9] : _styleBindings$key2$2, _styleBindings$key2$3 = _styleBindings$key2[2], options = _styleBindings$key2$3 === void 0 ? {} : _styleBindings$key2$3; var bounds = _this2.domElement.getBoundingClientRect(); var parsedArr1 = getArrayValues(arr1, windowWidth).map(function (i) { return parseValue(i, bounds, _this2.groupIndex); }); var parsedArr2 = getArrayValues(arr2, windowWidth).map(function (i) { return parseValue(i, bounds, _this2.groupIndex); }); parsedStyleBindings[key] = [parsedArr1, parsedArr2, options]; }; for (var key in styleBindings) { _loop2(key); } _this2.transforms[driverName] = parsedStyleBindings; }; for (var driverName in _this2.transformsData) { _loop(driverName); } }); _defineProperty(this, "applyStyles", function (styles) { var mergedStyles = flattenStyles(styles); Object.keys(mergedStyles).forEach(function (key) { _this2.domElement.style.setProperty(key, mergedStyles[key]); }); }); this.selector = selector; this.laxInstance = laxInstance; this.domElement = domElement; this.transformsData = transformsData; this.groupIndex = groupIndex; var _options$style = _options.style, style = _options$style === void 0 ? {} : _options$style, onUpdate = _options.onUpdate; Object.keys(style).forEach(function (key) { domElement.style.setProperty(key, style[key]); }); if (onUpdate) this.onUpdate = onUpdate; this.calculateTransforms(); }; var Lax = function Lax() { var _this3 = this; _classCallCheck(this, Lax); _defineProperty(this, "drivers", []); _defineProperty(this, "elements", []); _defineProperty(this, "frame", 0); _defineProperty(this, "debug", false); _defineProperty(this, "windowWidth", 0); _defineProperty(this, "windowHeight", 0); _defineProperty(this, "presets", laxPresets); _defineProperty(this, "debugData", { frameLengths: [] }); _defineProperty(this, "init", function () { _this3.findAndAddElements(); window.requestAnimationFrame(_this3.onAnimationFrame); _this3.windowWidth = document.body.clientWidth; _this3.windowHeight = document.body.clientHeight; window.onresize = _this3.onWindowResize; }); _defineProperty(this, "onWindowResize", function () { var changed = document.body.clientWidth !== _this3.windowWidth || document.body.clientHeight !== _this3.windowHeight; if (changed) { _this3.windowWidth = document.body.clientWidth; _this3.windowHeight = document.body.clientHeight; _this3.elements.forEach(function (el) { return el.calculateTransforms(); }); } }); _defineProperty(this, "onAnimationFrame", function (e) { if (_this3.debug) { _this3.debugData.frameStart = Date.now(); } var driverValues = {}; _this3.drivers.forEach(function (driver) { driverValues[driver.name] = driver.getValue(_this3.frame); }); _this3.elements.forEach(function (element) { element.update(driverValues, _this3.frame); }); if (_this3.debug) { _this3.debugData.frameLengths.push(Date.now() - _this3.debugData.frameStart); } if (_this3.frame % 60 === 0 && _this3.debug) { var averageFrameTime = Math.ceil(_this3.debugData.frameLengths.reduce(function (a, b) { return a + b; }, 0) / 60); console.log("Average frame calculation time: ".concat(averageFrameTime, "ms")); _this3.debugData.frameLengths = []; } _this3.frame++; window.requestAnimationFrame(_this3.onAnimationFrame); }); _defineProperty(this, "addDriver", function (name, getValueFn) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; _this3.drivers.push(new LaxDriver(name, getValueFn, options)); }); _defineProperty(this, "removeDriver", function (name) { _this3.drivers = _this3.drivers.filter(function (driver) { return driver.name !== name; }); }); _defineProperty(this, "findAndAddElements", function () { _this3.elements = []; var elements = document.querySelectorAll(".lax"); elements.forEach(function (domElement) { var driverName = "scrollY"; var presets = []; domElement.classList.forEach(function (className) { if (className.includes("lax_preset")) { var preset = className.replace("lax_preset_", ""); presets.push(preset); } }); var transforms = _defineProperty({}, driverName, { presets: presets }); _this3.elements.push(new LaxElement('.lax', _this3, domElement, transforms, 0, {})); }); }); _defineProperty(this, "addElements", function (selector, transforms, options) { var domElements = document.querySelectorAll(selector); domElements.forEach(function (domElement, i) { _this3.elements.push(new LaxElement(selector, _this3, domElement, transforms, i, options)); }); }); _defineProperty(this, "removeElements", function (selector) { _this3.elements = _this3.elements.filter(function (element) { return element.selector !== selector; }); }); _defineProperty(this, "addElement", function (domElement, transforms, options) { _this3.elements.push(new LaxElement('', _this3, domElement, transforms, 0, options)); }); _defineProperty(this, "removeElement", function (domElement) { _this3.elements = _this3.elements.filter(function (element) { return element.domElement !== domElement; }); }); }; return new Lax(); }(); if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') module.exports = laxInstance;else window.lax = laxInstance; })(); ================================================ FILE: package.json ================================================ { "name": "lax.js", "version": "2.0.3", "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" }, "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", "uglify-js": "^3.9.4" }, "description": "Simple & lightweight (<4kb gzipped) vanilla JavaScript library to create smooth & beautiful animations when you scroll.", "license": "MIT", "author": "alexfoxy@gmail.com", "repository": { "type": "git", "url": "https://github.com/alexfoxy/lax.js" }, "main": "lib/lax.min.js", "keywords": [ "javascript", "lax", "laxxx", "lax.js", "laxjs", "parallax", "scroll", "animation", "effects", "css", "html" ], "dependencies": {} } ================================================ FILE: src/lax.js ================================================ (() => { 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 })()