Repository: iamvdo/stickyElements Branch: master Commit: 185064af6f8f Files: 12 Total size: 28.0 KB Directory structure: gitextract_ihm_xnm8/ ├── .gitignore ├── .npmignore ├── README.md ├── build.js ├── dist/ │ ├── stickyelements-animate.js │ └── stickyelements.js ├── package.json ├── src/ │ ├── StickyElement.js │ └── stickyElements.js └── test/ ├── index.html ├── test.html └── test.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ node_modules ================================================ FILE: .npmignore ================================================ node_modules demo test .gitignore build.js ================================================ FILE: README.md ================================================ # Sticky Elements Everything can stick. Just because. [Demo](http://design.iamvdo.me/stickyElements) ## Getting started `npm install stickyelements` and insert `dist/stickyelements-animate.js` (or build your own bundle using `src` files) Then, stick elements! ```javascript stickyElements('.item', { stickiness: 5, duration: 450 }); ``` ## Arguments ### CSS selector * Elements that will stick to your mouse ### Options * `stickiness` [Integer, Object]: How long elements remain stick to your mouse. If integer, apply same `x` and `y` values. If object, can contain `x` and/or `y` key. Integer between 0 and 10. (Default: `3`) * `duration` [Integer]: Duration in milliseconds of animation (using [animateplus](https://github.com/bendc/animateplus). (Default: `450`) * `pointer` [Boolean]: Enable Pointer Events instead of Mouse Events. Elements will stick to mouse, touch and all input types (Default: `false`). Need [PEP polyfill](https://github.com/jquery/PEP) and `touch-action` attribute on each elements (follow PEP polyfill instructions). ================================================ FILE: build.js ================================================ var fs = require('fs'); var babel = require('rollup-plugin-babel'); var rollup = require('rollup'); var uglify = require('uglify-js'); var args = process.argv; var packageFile = JSON.parse(fs.readFileSync('package.json', 'utf8')); var version = packageFile.version; var files = { src: './src/stickyElements.js', srcAnimate: './node_modules/animateplus/animate.min.js', dist: './dist/stickyelements.js', distAnimate: './dist/stickyelements-animate.js' } function minify (code) { return uglify.minify(code, {fromString: true}).code; } function runBuild () { fs.mkdir('./dist', (err) => { rollup.rollup({ entry: files.src, plugins: [ babel({presets: [ 'es2015-rollup' ]}) ] }).then(function (bundle) { var result = bundle.generate({ format: 'iife', moduleName: 'stickyElements' }); var code = minify(result.code); fs.writeFile(files.dist, code, 'utf8'); fs.readFile(files.srcAnimate, 'utf8', (err, animate) => { fs.writeFile(files.distAnimate, animate + code, 'utf8'); }); }, function (err) { console.log(err); }); }); } runBuild(); if (args[2] && args[2] === '-w') { fs.watchFile(files.src, runBuild); fs.watchFile('./src/StickyElement.js', runBuild); } ================================================ FILE: dist/stickyelements-animate.js ================================================ var animate=function(){function F(a){return Array.isArray(a)?a:Array.prototype.slice.call(a)}var k=function(a){var c=a.length;return function d(){for(var e=arguments.length,f=Array(e),g=0;g=arguments.length||void 0===arguments[4]?500:arguments[4];if(0==a)return c;if(1==(a/=d))return c+b;var e=d*(1-Math.min(e,999)/1E3),f=b=arguments.length||void 0===arguments[4]?500:arguments[4];if(0==a)return c;if(1==(a/=d))return c+b;e=d*(1-Math.min(e,999)/1E3);return b*Math.pow(2, -10*a)*Math.sin(2*(a*d-(b(a/=d/2)?b/2*a*a+c:-b/2*(--a*(a-2)-1)+c},easeInOutCubic:function(a,c,b,d){return 1>(a/=d/2)?b/2*a* a*a+c:b/2*((a-=2)*a*a+2)+c},easeInOutQuart:function(a,c,b,d){return 1>(a/=d/2)?b/2*a*a*a*a+c:-b/2*((a-=2)*a*a*a-2)+c},easeInOutQuint:function(a,c,b,d){return 1>(a/=d/2)?b/2*a*a*a*a*a+c:b/2*((a-=2)*a*a*a*a+2)+c},easeInOutSine:function(a,c,b,d){return-b/2*(Math.cos(Math.PI*a/d)-1)+c},easeInOutExpo:function(a,c,b,d){return 0==a?c:a==d?c+b:1>(a/=d/2)?b/2*Math.pow(2,10*(a-1))+c:b/2*(-Math.pow(2,-10*--a)+2)+c},easeInOutCirc:function(a,c,b,d){return 1>(a/=d/2)?-b/2*(Math.sqrt(1-a*a)-1)+c:b/2*(Math.sqrt(1- (a-=2)*a)+1)+c},easeInOutElastic:function(a,c,b,d){var e=4>=arguments.length||void 0===arguments[4]?500:arguments[4];if(0==a)return c;if(2==(a/=d/2))return c+b;var e=d*(1-Math.min(e,999)/1E3)*1.5,f=ba?-.5*b*Math.pow(2,10*--a)*Math.sin(2*(a*d-f)*Math.PI/e)+c:b*Math.pow(2,-10*--a)*Math.sin(2*(a*d-f)*Math.PI/e)*.5+b+c},easeInOutBack:function(a,c,b,d){var e=1.70158;return 1>(a/=d/2)?b/2*a*a*(((e*=1.525)+1)*a-e)+c:b/2*((a-=2)*a*(((e*=1.525)+1)*a+e)+ 2)+c}},y=function(a){return a[0]},H=function(a){return a.reduce(function(a,b){return a.concat(b)})},n=function(){return Array.prototype.includes?function(a,c){return a.includes(c)}:function(a,c){return a.some(function(a){return a===c})}}(),z=function(a){for(var c=arguments.length,b=Array(1a.length?a.split("").reduce(function(a,b){return a+b+b}):a},c=function(a){return a.match(/[\d\w]{2}/g).map(function(a){return parseInt(a,16)})};return function(b){if(A(b))return b;b=r(a,c)(b);return"rgb("+b[0]+", "+b[1]+", "+b[2]+")"}}(),B=function(a){a="string"==typeof a?/^[\#.]?[\w-]+$/.test(a)?"."==a[0]?document.getElementsByClassName(a.slice(1)):"#"==a[0]?document.getElementById(a.slice(1)):document.getElementsByTagName(a):document.querySelectorAll(a):a;return Array.isArray(a)? a:a.nodeType?[a]:a instanceof NodeList||a instanceof HTMLCollection?[].concat(F(a)):a.get()},m=new Map;"el delay begin complete loop direction".split(" ").forEach(function(a){return m.set(a,null)});m.set("duration",1E3);m.set("easing","easeOutElastic");var L=function(){var a=q(m).filter(function(a){return m.get(a)}),c=function(b){return a.every(function(a){return b.has(a)})},b=function(b){var c=h(b);a.forEach(function(a){c.has(a)||c.set(a,m.get(a))});return c};return function(a){return c(a)?a:b(a)}}(), M=function(){var a=k(function(a,b){return Array.isArray(a.get(b))}),c=function(b){return p(b).every(a(b))},b=function(b){return p(b).filter(t(a(b)))};return function(a){if(c(a))return a;var e=h(a);b(e).forEach(function(a){return e.set(a,[C.get(a),e.get(a)])});return e}}(),N=function(){var a=function(a){return/\D$/.test(a)},c=k(function(b,c){return a(c)||/scale/.test(b)?c:/rotate|skew/.test(b)?c+"deg":c+"px"}),b=function(b,c){return c.every(function(c){return b.get(c).every(a)})};return function(a){var e= p(a).filter(u);if(b(a,e))return a;var f=h(a);e.forEach(function(b){return f.set(b,a.get(b).map(c(b)))});return f}}(),O=function(){var a=k(function(a,b){return a.get(b).some(J)}),c=function(b){return!D(b).some(a(b))},b=function(b){return D(b).filter(a(b))};return function(a){if(c(a))return a;var e=h(a);b(a).forEach(function(a){return e.set(a,e.get(a).map(K))});return e}}(),E=function(a){var c=h(a);v(a).forEach(function(a){return c.set(a,c.get(a).slice().reverse())});return c},P=r(I,L,M,N,O,function(a){var c= h(a);c.set("el",B(a.get("el")));return c},function(a){return"reverse"==a.get("direction")?E(a):a}),v=function(){var a=q(m),c=function(b){return t(n)(a,b)};return function(a){return q(a).filter(c)}}(),R=function(){var a=r(y,A),c=k(function(b,c){var e=b.get(c).map(Q),f=e[0],g=e[1],e=new Map;e.set("prop",c);e.set("from",f);e.set("to",g);e.set("isTransformFunction",u(c));e.set("isColor",a(b.get(c)));/\d$/.test(b.get("easing"))?(f=b.get("easing").split(" "),g=f[1],e.set("easing",f[0]),e.set("frequency", g)):e.set("easing",b.get("easing"));return e});return function(a,d){return v(a).map(c(a))}}(),p=function(){var a=function(a){return n(w,a)};return function(c){return q(c).filter(a)}}(),D=function(a){return z(v(a),p(a))},w="opacity translateX translateY scale rotate scaleX scaleY rotateX rotateY perspective skewX skewY translateZ rotateZ scaleZ".split(" "),C=new Map;w.forEach(function(a){return C.set(a,n(["opacity","scale","scaleX","scaleY"],a)?1:0)});var u=function(){var a=w.filter(function(a){return"opacity"!= a});return function(c){return n(a,c)}}(),S=function(a){var c=p(a);if(c.length){var b=[];c.some(u)&&b.push("transform");n(c,"opacity")&&b.push("opacity");var d=b.join();a.get("el").forEach(function(a){a.style.willChange||(a.style.willChange=d)})}},T=function(a,c){return c.reduce(function(b,c,e){return b+a[e-1]+c})},Q=function(){var a=/-?\d*\.?\d+/g;return function(c){var b=new Map;b.set("digits",("string"==typeof c?c:String(c)).match(a).map(Number));b.set("others",("string"==typeof c?c:String(c)).split(a)); return b}}(),U=k(function(a,c,b){var d=b.get("to").get("digits").map(function(d,f){var g=b.get("from").get("digits")[f];if(g==d)return g;var h=d-g,g=G[b.get("easing")](c,g,h,a.get("duration"),b.get("frequency"));return b.get("isColor")?Math.round(g):g});return T(d,b.get("to").get("others"))}),V=k(function(a,c){var b=a.get(c.get("prop"));return y(b.slice(-1))}),W=function(){var a=void 0;return k(function(c,b,d){var e=void 0;c.forEach(function(a,c){a.get("isTransformFunction")?(e||(e=[]),e.push(a.get("prop")+ "("+b[c]+")")):"opacity"==a.get("prop")?d.style.opacity=b[c]:d.setAttribute(a.get("prop"),b[c])});e&&(a||(a="transform"in document.body.style?"transform":"-webkit-transform"),d.style[a]=e.join(" "))})}(),X=function(){var a=function(a,b){b.get("begin")&&b.get("begin")(b.get("el"));requestAnimationFrame(a)};return function(c,b){return b.get("delay")?setTimeout(function(){return a(c,b)},b.get("delay")):a(c,b)}}(),Y=function(a){return x(function(){if("alternate"==a.get("direction"))return E(a);if("reverse"== a.get("direction")){var c=h(a);c["delete"]("direction");return c}return a}())},l=new Map,Z=function(){var a=0;return function(c){var b=a++,d=h(l);d.set(b,c);l=d;return b}}(),x=function(a){var c=P(a),b=R(c),d=Z(c.get("el")),e=new Map;S(c);X(function g(a){if(l.has(d)){e.has("start")||e.set("start",a);e.set("elapsed",a-e.get("start"));a=e.get("elapsed")", "license": "MIT", "scripts": { "build": "node ./build.js", "watch": "node ./build.js -w" }, "devDependencies": { "animateplus": "^1.0.0", "babel-preset-es2015-rollup": "^1.1.1", "mocha": "^2.0.0", "rollup": "^0.25.2", "rollup-plugin-babel": "^2.3.9", "uglify-js": "^2.6.1" } } ================================================ FILE: src/StickyElement.js ================================================ export default class StickyElement { constructor (el, opts = {}) { this.el = el; this.setOpts(opts); this.setEvents(); } setOpts (opts) { this.pointer = opts.pointer; this.positions = {}; this.isGripped = false; this.stickiness = {}; this.grip = {x: 3, y: 4}; this.duration = opts.duration || 450; this.setStickiness(opts); } setEvents () { let el = this.el; const events = ['enter', 'leave', 'move']; // prevent events to be registered multiple times if (el._stickyEvents) { events.map(e => { if (this.pointer) { el.removeEventListener('pointer' + e, el._stickyEvents[e], false); } else { el.removeEventListener('mouse' + e, el._stickyEvents[e], false); } }); el._stickyEvents = undefined; } el._stickyEvents = { enter: (e) => this.onEnter(e), leave: (e) => this.onLeave(e), move: (e) => this.onMove(e) }; events.map(e => { if (this.pointer) { el.addEventListener('pointer' + e, el._stickyEvents[e], false); } else { el.addEventListener('mouse' + e, el._stickyEvents[e], false); } }); } setStickiness (opts) { const forces = { 1: 10.0, 2: 6.6, 3: 4.5, 4: 3.2, 5: 2.4, 6: 1.9, 7: 1.6, 8: 1.4, 9: 1.3, 10: 1.2, 0: 0.0 }; let opt = {stickiness: {x: 3, y: 3}}; // if no options specified if (opts.stickiness || opts.stickiness === 0) { if (typeof opts.stickiness === "number") { opt.stickiness = {x: opts.stickiness, y: opts.stickiness}; } if (opts.stickiness.x || opts.stickiness.x === 0) { opt.stickiness.x = opts.stickiness.x; } if (opts.stickiness.y || opts.stickiness.y === 0) { opt.stickiness.y = opts.stickiness.y; } } this.stickiness.x = forces[Math.min(10, opt.stickiness.x)]; this.stickiness.y = forces[Math.min(10, opt.stickiness.y)]; } getPositions (deltax, deltay) { const posx = (this.stickiness.x !== 0) ? deltax / this.stickiness.x : 0; const posy = (this.stickiness.y !== 0) ? deltay / this.stickiness.y : 0; return {posx, posy}; } onEnter (event) { const {offsetWidth, offsetHeight, offsetLeft, offsetTop} = this.el; const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const positions = { width: offsetWidth, height: offsetHeight, centerx: offsetLeft + (offsetWidth / 2), centery: offsetTop + (offsetHeight / 2) - scrollTop }; this.positions = positions; } onLeave (event) { // prevent mouseleave event to be triggered twice by 30ms if (this.lastGripped) { const now = new Date().getTime(); if (now - this.lastGripped < 30) { return; } } const element = this.el; animate.stop(element); const {posx, posy} = this.getPositions(this.positions.deltax, this.positions.deltay); if (this.isGripped) { this.lastGripped = new Date().getTime(); animate({ el: element, translateX: [posx, 0], translateY: [posy, 0], duration: this.duration }); } this.isGripped = false; } onMove (event) { const element = this.el; animate.stop(element); const {clientX, clientY} = event; const isGrip = { x: Math.abs(this.positions.deltax) < (this.positions.width / this.grip.x), y: Math.abs(this.positions.deltay) < (this.positions.height / this.grip.y) }; if (isGrip.x && isGrip.y) { this.isGripped = true; } this.positions.deltax = -(this.positions.centerx - clientX); this.positions.deltay = -(this.positions.centery - clientY); if (this.isGripped) { const {posx, posy} = this.getPositions(this.positions.deltax, this.positions.deltay); element.style.transform = 'translate3d(' + posx + 'px, ' + posy + 'px, 0)'; } } } ================================================ FILE: src/stickyElements.js ================================================ import StickyElement from './StickyElement'; export default (() => { return (selector, opts) => { const stickyElementsTab = []; const elements = [].slice.call(document.querySelectorAll(selector)); for (let i = 0; i < elements.length; i++) { stickyElementsTab.push(new StickyElement(elements[i], opts)); }; return stickyElementsTab; }; })(); ================================================ FILE: test/index.html ================================================ Live test

Sticky Elements Playground

This should be a sticky link. And another one

================================================ FILE: test/test.html ================================================ Tests
================================================ FILE: test/test.js ================================================ describe('stickyElements', function() { describe('Lib', function () { it('should set one stickyElements', function () { var se = stickyElements('.mocha'); expect(se.length).to.eql(1); }); it('should set multiple stickyElements', function () { var se = stickyElements('.multiple'); expect(se.length).to.eql(2); }); it('should create stickyElements instance', function () { var se = stickyElements('.multiple'); for (var i = 0; i < se.length; i++) { expect(se[i]).to.have.keys('el', 'stickiness'); expect(se[i].el).to.be.an(HTMLElement); }; }); }); describe('Options', function () { it('should set default options', function () { var se = stickyElements('.mocha'); var stickiness = se[0].stickiness; expect(stickiness).to.eql({x: 4.5, y: 4.5}); }); it('should set default options to multiple elements', function () { var se = stickyElements('.multiple'); for (var i = 0; i < se.length; i++) { var stickiness = se[i].stickiness; expect(stickiness).to.eql({x: 4.5, y: 4.5}); }; }); it('should set default options to multiple elements with options', function () { var se = stickyElements('.multiple', {}); for (var i = 0; i < se.length; i++) { var stickiness = se[i].stickiness; expect(stickiness).to.eql({x: 4.5, y: 4.5}); }; }); it('should overidde options', function () { var se = stickyElements('.mocha', {stickiness: {x: 2, y: 4}}); var stickiness = se[0].stickiness; expect(stickiness).to.eql({x: 6.6, y: 3.2}); }); it('should overidde only X option', function () { var se = stickyElements('.mocha', {stickiness: {x: 2}}); var stickiness = se[0].stickiness; expect(stickiness).to.eql({x: 6.6, y: 4.5}); }); it('should overidde only Y option', function () { var se = stickyElements('.mocha', {stickiness: {y: 4}}); var stickiness = se[0].stickiness; expect(stickiness).to.eql({x: 4.5, y: 3.2}); }); it('should accept integer', function () { var se = stickyElements('.mocha', {stickiness: 2}); var stickiness = se[0].stickiness; expect(stickiness).to.eql({x: 6.6, y: 6.6}); }); it('should accept 0', function () { var se = stickyElements('.mocha', {stickiness: 0}); var stickiness = se[0].stickiness; expect(stickiness).to.eql({x: 0.0, y: 0.0}); }); it('should accept 0, only X', function () { var se = stickyElements('.mocha', {stickiness: {x: 0}}); var stickiness = se[0].stickiness; expect(stickiness).to.eql({x: 0.0, y: 4.5}); }); it('should accept 0, only Y', function () { var se = stickyElements('.mocha', {stickiness: {y: 0}}); var stickiness = se[0].stickiness; expect(stickiness).to.eql({x: 4.5, y: 0.0}); }); it('should limit to 10', function () { var se = stickyElements('.mocha', {stickiness: 12}); var stickiness = se[0].stickiness; expect(stickiness).to.eql({x: 1.2, y: 1.2}); }); }); });