From 3141d2c445bb60c335b2a943eb467f34e084e884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=B6nthal?= Date: Thu, 31 Aug 2017 16:35:49 +0200 Subject: [PATCH] add treshold option, fixed cs --- .eslintrc.json | 4 +- README.md | 94 +++++++++-------- dist/scrollmap.js | 26 +++-- dist/trigger.js | 62 +++++------ package.json | 12 ++- src/scrollmap.js | 258 +++++++++++++++++++++++++--------------------- src/trigger.js | 44 ++++---- 7 files changed, 275 insertions(+), 225 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index fea36d9..0fa8526 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -230,7 +230,7 @@ ], "linebreak-style": [ 2, - "windows" + "unix" ], "max-nested-callbacks": [ 2, @@ -367,4 +367,4 @@ "prefer-template": 2, "require-yield": 2 } -} \ No newline at end of file +} diff --git a/README.md b/README.md index 6b38c32..bbb2211 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,20 @@ A module for testing if a DOM element is visible in the viewport, then triggers Using ES6: - import Scrollmap from 'scrollmap'; +```javascript +import Scrollmap from 'scrollmap'; +``` Using a CDN via jsDelivr: - - +```html + + +``` ******************************************** -##Method - trigger(options, callback) +## Method - trigger(options, callback) **Description:** A method for adding triggers when element is visible in the viewport. @@ -36,24 +40,29 @@ as long as the scroll event is happening. **alwaysRunOnTrigger (boolean)**: by default the triggered element callback will only be executed one time. Setting to true will re-trigger the callback everytime the element has been in and out of the viewport. +**treshold (number)**: add this much pixels to calculation to the visibility check (useful if you want to execute code just before the element becomes visible) + **callback (object)**: This is the function which will be exectued when the element is detected in the viewport. To reference the node, pass it into the callback as an argument. **EXAMPLE** - Scrollmap.trigger({ - target: '.collection-list .items', - surfaceVisible: 0.5, - runOnScroll: true, - alwaysRunOnTrigger: true - }, (element) => { - $(element).addClass("visible"); - }); +```javascript +Scrollmap.trigger({ + target: '.collection-list .items', + surfaceVisible: 0.5, + runOnScroll: true, + alwaysRunOnTrigger: true, + treshold: 200 +}, (element) => { + $(element).addClass("visible"); +}); +``` ******************************************** -##Method - sequence(options, callback) +## Method - sequence(options, callback) **Description:** A method for staggering an array of triggers. @@ -71,52 +80,55 @@ can get the item and index of the array as arguments **EXAMPLE** - Scrollmap.trigger({ - target: ".boxes", - surfaceVisible: 0.2 - }, (element) => { +```javascript +Scrollmap.trigger({ + target: ".boxes", + surfaceVisible: 0.2 +}, (element) => { - //define the array of the elements to sequence + //define the array of the elements to sequence - const array = element.querySelectorAll(".box"); + const array = element.querySelectorAll(".box"); - //use the sequence method to define, interval and callback - //function. + //use the sequence method to define, interval and callback + //function. - Scrollmap.sequence(array, { - interval: 5, - order: "random" - }, (item) => { + Scrollmap.sequence(array, { + interval: 5, + order: "random" + }, (item) => { - //add any code to be triggered when - //the element is in the viewport + //add any code to be triggered when + //the element is in the viewport - item.classList.add("color-change"); + item.classList.add("color-change"); - }); - }); + }); +}); +``` ******************************************** -##Method - out(function) +## Method - out(function) When the trigger is has been executed and the element is no longer in the viewport, the out method can be chained to the trigger to execute the specified function. **EXAMPLE** - Scrollmap.trigger({ - target: ".boxes", - surfaceVisible: 0.2 - }, (element) => { - element.classList.add("foo"); - }).out((element) => { - element.classList.add("bar"); - }); - +```javascript +Scrollmap.trigger({ + target: ".boxes", + surfaceVisible: 0.2 +}, (element) => { + element.classList.add("foo"); +}).out((element) => { + element.classList.add("bar"); +}); +``` ******************************************** -##Hooks +## Hooks **data-scrollmap-loaded (boolean):** Once the element is initialized. diff --git a/dist/scrollmap.js b/dist/scrollmap.js index 94ef103..2709694 100644 --- a/dist/scrollmap.js +++ b/dist/scrollmap.js @@ -20,7 +20,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons * @namespace scrollMap * @description store element points and check if * elements are visible -*/ + */ var Scroll_Event_Trigger = function () { function Scroll_Event_Trigger() { @@ -157,7 +157,7 @@ var Scroll_Event_Trigger = function () { } }, { key: "elementInViewport", - value: function elementInViewport(el, percetageOfElement) { + value: function elementInViewport(el, percetageOfElement, treshold) { /* * @desc check if element is in viewport @@ -173,7 +173,7 @@ var Scroll_Event_Trigger = function () { var rect = el.getBoundingClientRect(); var stats = { - top: rect.top - window.innerHeight, + top: rect.top - window.innerHeight + treshold, bottom: rect.bottom + rect.height, height: rect.height }; @@ -188,7 +188,7 @@ var Scroll_Event_Trigger = function () { }, { key: "checkVisible", value: function checkVisible(point) { - var viewport = this.elementInViewport(point.element, point.surfaceVisible); + var viewport = this.elementInViewport(point.element, point.surfaceVisible, point.treshold); if (viewport) { this.setTriggerIn(point); @@ -234,8 +234,20 @@ var Scroll_Event_Trigger = function () { value: function events() { var _this2 = this; + var supportsPassive = false; + + try { + var opts = Object.defineProperty({}, "passive", { + get: function get() { + supportsPassive = true; + } + }); + + window.addEventListener("test", null, opts); + } catch (e) {} // eslint-disable-line no-empty + // initial check on page load to see if elements are visible - window.addEventListener('load', function () { + window.addEventListener("load", function () { _this2.points.forEach(function (point) { _this2.checkVisible(point); }); @@ -247,15 +259,13 @@ var Scroll_Event_Trigger = function () { _this2.points.forEach(function (point) { _this2.checkVisible(point); }); - }); + }, supportsPassive ? { passive: true } : false); } }]); return Scroll_Event_Trigger; }(); -; - var Scrollmap = new Scroll_Event_Trigger(); window.Scrollmap = Scrollmap; diff --git a/dist/trigger.js b/dist/trigger.js index b2bc96b..d142927 100644 --- a/dist/trigger.js +++ b/dist/trigger.js @@ -1,7 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { - value: true + value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); @@ -9,36 +9,36 @@ var _createClass = function () { function defineProperties(target, props) { for function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Trigger = function () { - function Trigger(element, options, callback) { - _classCallCheck(this, Trigger); - - this.element = element; - this.surfaceVisible = 0.5; - this.callback = callback; - this.triggeredIn = false; - this.triggeredOut = false; - this.runOnScroll = false; - this.alwaysRunOnTrigger = false; - if (options) { - Object.assign(this, options); - } - } - - _createClass(Trigger, [{ - key: "onTriggerIn", - value: function onTriggerIn() { - this.callback(this.element); - return this; - } - }, { - key: "destroy", - value: function destroy() { - this.element = null; - this.isDestroyed = true; - } - }]); - - return Trigger; + function Trigger(element, options, callback) { + _classCallCheck(this, Trigger); + + this.element = element; + this.surfaceVisible = 0.5; + this.treshold = 0; + this.callback = callback; + this.triggeredIn = false; + this.triggeredOut = false; + this.runOnScroll = false; + this.alwaysRunOnTrigger = false; + if (options) { + Object.assign(this, options); + } + } + + _createClass(Trigger, [{ + key: "onTriggerIn", + value: function onTriggerIn() { + this.callback(this.element); + return this; + } + }, { + key: "destroy", + value: function destroy() { + this.element = null; + } + }]); + + return Trigger; }(); exports.default = Trigger; \ No newline at end of file diff --git a/package.json b/package.json index 25d2949..f9263d5 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,11 @@ "description": "A module for testing if an element is visible in the viewport, then triggers callbacks on execution.", "main": "./dist/scrollmap.js", "scripts": { - "start": "set PROD_ENV=false&&webpack --progress --colors --watch", + "start": "PROD_ENV=false webpack --progress --colors --watch", "watch": "./node_modules/.bin/webpack -d --watch --colors", - "build": "set PROD_ENV=true&&babel src -d dist", - "dev": "set PROD_ENV=false&&webpack-dev-server ./src/scrollmap.js --hot --watch --colors", - "cdn" : "set PROD_ENV=false&&webpack&&set PROD_ENV=true&&webpack -p", + "build": "PROD_ENV=true babel src -d dist", + "dev": "PROD_ENV=false webpack-dev-server ./src/scrollmap.js --hot --watch --colors", + "cdn": "PROD_ENV=false webpack && PROD_ENV=true webpack -p", "lint": "eslint src" }, "repository": { @@ -47,7 +47,9 @@ "webpack-dev-server": "^2.4.2" }, "babel": { - "presets": ["latest"] + "presets": [ + "latest" + ] }, "keywords": [ "element", diff --git a/src/scrollmap.js b/src/scrollmap.js index 0bb387c..e9a1aec 100644 --- a/src/scrollmap.js +++ b/src/scrollmap.js @@ -1,10 +1,10 @@ import Trigger from "./trigger"; - /** - * @namespace scrollMap - * @description store element points and check if - * elements are visible - */ +/** + * @namespace scrollMap + * @description store element points and check if + * elements are visible + */ class Scroll_Event_Trigger { constructor () { @@ -12,92 +12,98 @@ class Scroll_Event_Trigger { this.points = []; this.events(); } + out (args) { this.onTriggerOut = args; return this; } + sequence (array, options, func) { - /* - * @desc run through an array of elements and apply a - * staggered sequence delay - */ - array = Array.prototype.slice.call(array); + /* + * @desc run through an array of elements and apply a + * staggered sequence delay + */ + array = Array.prototype.slice.call(array); - let delay = 0; + let delay = 0; - if (options.order) { - this.sequenceOrder(array, options.order); - } + if (options.order) { + this.sequenceOrder(array, options.order); + } - if (options.delay) { - delay = options.delay; - } + if (options.delay) { + delay = options.delay; + } - const run = array.forEach((item, i) => { - setTimeout(() => { - func(array[ i ], i); - }, options.interval * i); - }); + const run = array.forEach((item, i) => { + setTimeout(() => { + func(array[ i ], i); + }, options.interval * i); + }); - setTimeout(run, delay); + setTimeout(run, delay); - return this; + return this; } + sequenceOrder (array, order) { - /* - * @desc randomize an array for a trigger sequence - */ + /* + * @desc randomize an array for a trigger sequence + */ - switch (order) { - case "random" : - array = array.sort(() => { - return 0.5 - Math.random(); - }); - break; - case "reverse" : - array = array.reverse(); - break; - default : - - } - return array; + switch (order) { + case "random" : + array = array.sort(() => { + return 0.5 - Math.random(); + }); + break; + case "reverse" : + array = array.reverse(); + break; + default : + + } + return array; } + trigger (args, callback) { - /* - * @desc add classname indicating element is intialized - */ - - let el = args.target; - - switch (typeof el) { - case "string": - el = document.querySelectorAll(el); - break; - case "object": - el = [el]; - break; - default: - el = document.querySelectorAll(el); - } - - el = this.toArray(el); - - el.forEach((node) => { - node.setAttribute("data-scrollmap-loaded", true); - node.setAttribute("data-scrollmap-triggered-in", false); - node.setAttribute("data-scrollmap-triggered-out", false); - const point = new Trigger(node, args, callback); - - this.points.push(point); - }); - return this; + /* + * @desc add classname indicating element is intialized + */ + + let el = args.target; + + switch (typeof el) { + case "string": + el = document.querySelectorAll(el); + break; + case "object": + el = [el]; + break; + default: + el = document.querySelectorAll(el); + } + + el = this.toArray(el); + + el.forEach((node) => { + node.setAttribute("data-scrollmap-loaded", true); + node.setAttribute("data-scrollmap-triggered-in", false); + node.setAttribute("data-scrollmap-triggered-out", false); + const point = new Trigger(node, args, callback); + + this.points.push(point); + }); + return this; } + toArray (collection) { return Array.prototype.slice.call(collection); } + setTriggerIn (point) { point.element.setAttribute("data-scrollmap-is-visible", true); point.element.setAttribute("data-scrollmap-triggered-in", true); @@ -109,6 +115,7 @@ class Scroll_Event_Trigger { } } } + setTriggerOut (point) { point.element.setAttribute("data-scrollmap-is-visible", false); point.element.setAttribute("data-scrollmap-triggered-out", true); @@ -121,43 +128,46 @@ class Scroll_Event_Trigger { point.triggeredOut = true; } } - elementInViewport (el, percetageOfElement) { - /* - * @desc check if element is in viewport - */ + elementInViewport (el, percetageOfElement, treshold) { + + /* + * @desc check if element is in viewport + */ - /* - * look for direction of scroll and base element visible - * percentage off of either top bottom when scrolling - * down, or the top when scrolling up. This may not be - * the perfect method but is cross browser compatible. - */ + /* + * look for direction of scroll and base element visible + * percentage off of either top bottom when scrolling + * down, or the top when scrolling up. This may not be + * the perfect method but is cross browser compatible. + */ - const rect = el.getBoundingClientRect(); + const rect = el.getBoundingClientRect(); - const stats = { - top: rect.top - window.innerHeight, - bottom: rect.bottom + rect.height, - height: rect.height - }; + const stats = { + top: rect.top - window.innerHeight + treshold, + bottom: rect.bottom + rect.height, + height: rect.height + }; - const amount = stats.height * percetageOfElement; + const amount = stats.height * percetageOfElement; - if ( (stats.bottom - amount > stats.height) && (stats.top + amount < 0)) { - return true; - } - return false; + if ((stats.bottom - amount > stats.height) && (stats.top + amount < 0)) { + return true; + } + return false; } + checkVisible (point) { - const viewport = this.elementInViewport(point.element, point.surfaceVisible); + const viewport = this.elementInViewport(point.element, point.surfaceVisible, point.treshold); - if (viewport) { - this.setTriggerIn(point); - } else { - this.setTriggerOut(point); - } + if (viewport) { + this.setTriggerIn(point); + } else { + this.setTriggerOut(point); + } } + on (string, callback) { /* * methods for creating various listeners @@ -172,6 +182,7 @@ class Scroll_Event_Trigger { } return this; } + scrollDirection () { /* * return the scroll direction via a string value @@ -179,34 +190,47 @@ class Scroll_Event_Trigger { let direction = ""; const st = window.pageYOffset || document.documentElement.scrollTop; - if (st > this.lastScrollTop) { - direction = "Down"; - } else { - direction = "Up"; - } - this.lastScrollTop = st; - return direction; + if (st > this.lastScrollTop) { + direction = "Down"; + } else { + direction = "Up"; + } + this.lastScrollTop = st; + return direction; } + events () { - // initial check on page load to see if elements are visible - window.addEventListener('load', () => { - this.points.forEach((point) => { - this.checkVisible(point); - }); - }, false); + let supportsPassive = false; - // check for visible elements on scroll - window.addEventListener("scroll", () => { - this.scrollOrient = this.scrollDirection(); - this.points.forEach((point) => { - this.checkVisible(point); - }); - }); + try { + const opts = Object.defineProperty({}, "passive", { + get () { + supportsPassive = true; + } + }); + + window.addEventListener("test", null, opts); + } catch (e) {} // eslint-disable-line no-empty + + // initial check on page load to see if elements are visible + window.addEventListener("load", () => { + this.points.forEach((point) => { + this.checkVisible(point); + }); + }, false); + + // check for visible elements on scroll + window.addEventListener("scroll", () => { + this.scrollOrient = this.scrollDirection(); + this.points.forEach((point) => { + this.checkVisible(point); + }); + }, supportsPassive ? { passive: true } : false); } -}; +} const Scrollmap = new Scroll_Event_Trigger(); window.Scrollmap = Scrollmap; -export default Scrollmap; \ No newline at end of file +export default Scrollmap; diff --git a/src/trigger.js b/src/trigger.js index e643aae..c0ecb6c 100644 --- a/src/trigger.js +++ b/src/trigger.js @@ -1,24 +1,26 @@ class Trigger { - constructor (element, options, callback) { - this.element = element; - this.surfaceVisible = 0.5; - this.callback = callback; - this.triggeredIn = false; - this.triggeredOut = false; - this.runOnScroll = false; - this.alwaysRunOnTrigger = false; - if (options) { - Object.assign(this, options); - } - } - onTriggerIn () { - this.callback(this.element); - return this; - } - destroy () { - this.element = null; - this.isDestroyed = true; - } + constructor (element, options, callback) { + this.element = element; + this.surfaceVisible = 0.5; + this.treshold = 0; + this.callback = callback; + this.triggeredIn = false; + this.triggeredOut = false; + this.runOnScroll = false; + this.alwaysRunOnTrigger = false; + if (options) { + Object.assign(this, options); + } + } + + onTriggerIn () { + this.callback(this.element); + return this; + } + + destroy () { + this.element = null; + } } -export default Trigger; \ No newline at end of file +export default Trigger;