diff --git a/.eslintrc.json b/.eslintrc.json index 1972d15..572987e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,7 @@ { "env": { "browser": true, + "jasmine": true, "node": true }, "extends": ["airbnb-base", "plugin:no-template-curly-in-string-fix/recommended"], @@ -12,15 +13,25 @@ } } ], + "plugins": ["jasmine"], "root": true, "rules": { "arrow-parens": ["error", "as-needed"], "import/extensions": [ "error", { + "mjs": "always", "js": "always" } ], + "import/resolver": [ + "error", + { + "node": { + "extensions": [".mjs", ".js"] + } + } + ], "indent": [ "error", "tab", diff --git a/Gemfile b/Gemfile index c9ed8e3..06af446 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,7 @@ gem 'middleman', '~> 4.2' gem 'middleman-autoprefixer', '~> 3.0' gem 'middleman-livereload' gem 'middleman-minify-html', '~> 3.4' +gem 'slim', '~> 4' gem 'terser' gem 'tzinfo-data', platforms: [:mswin, :mingw, :jruby, :x64_mingw] gem 'wdm', '~> 0.1', platforms: [:mswin, :mingw, :x64_mingw] diff --git a/Gemfile.lock b/Gemfile.lock index efd2b6a..50e8340 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -106,6 +106,9 @@ GEM sassc (2.4.0) ffi (~> 1.9) servolux (0.13.0) + slim (4.1.0) + temple (>= 0.7.6, < 0.9) + tilt (>= 2.0.6, < 2.1) temple (0.8.2) terser (1.1.13) execjs (>= 0.3.0, < 3) @@ -132,6 +135,7 @@ DEPENDENCIES middleman-autoprefixer (~> 3.0) middleman-livereload middleman-minify-html (~> 3.4) + slim (~> 4) terser tzinfo-data wdm (~> 0.1) diff --git a/assets/css/demo.sass b/assets/css/demo.sass new file mode 100644 index 0000000..fd6bba9 --- /dev/null +++ b/assets/css/demo.sass @@ -0,0 +1,82 @@ +@keyframes bg-rotate + 0%, 25% + filter: invert(0) + + 75%, 100% + filter: invert(1) + +@keyframes fade-in-out + 0% + opacity: 0.5 + transform: scale(0.6) + + 25% + opacity: 1 + transform: scale(1) + + 75% + opacity: 1 + transform: scale(2) + + 100% + opacity: 0.5 + transform: scale(5) + +.scrollerful--enabled + .scrollerful__plate > .scrollerful__sprite + animation-name: bg-rotate + align-items: center + background-color: #333 + display: flex + flex-flow: column + height: 100lvh + justify-content: center + overflow: hidden + width: 100vw + + > + h1, .piechart + &.scrollerful__sprite + animation-name: fade-in-out + + .piechart + $size: 10rem + background-image: conic-gradient(#fff 0%, #fff calc(var(--scrollerful-progress-inner, 0) * 100%), rgba(100%,100%,100%,0) calc(var(--scrollerful-progress-inner, 0) * 100%), rgba(100%,100%,100%,0) 100%) + border-radius: 100% + border: solid 0.2rem #fff + display: block + height: $size + width: $size + + .demo + &--large + .scrollerful__tray + height: 300vh + height: 300lvh + + &--medium + .scrollerful__tray + height: 100vh + height: 100lvh + + &--small + .scrollerful__tray + height: 50vh + height: 50lvh + + .scrollerful--x + &.demo + &--large + .scrollerful__tray + width: 300vw + width: 300lvw + + &--medium + .scrollerful__tray + width: 100vw + width: 100lvw + + &--small + .scrollerful__tray + width: 50vw + width: 50lvw diff --git a/assets/js/demo.js b/assets/js/demo.js new file mode 100644 index 0000000..caf5eea --- /dev/null +++ b/assets/js/demo.js @@ -0,0 +1,19 @@ +const showInnerProgress = ({ target, detail: { progress: { inner: progress } } }) => { + // eslint-disable-next-line no-param-reassign + target.querySelector('output').textContent = Math.max(0, Math.min(100, Math.round(progress * 100))) +} + +const main = () => { + Array.from(document.querySelectorAll('.scrollerful__tray')).forEach(el => { + if (!el.querySelector('output')) return + el.addEventListener('scrollerfulscroll', showInnerProgress) + }) +} + +(function init() { + if (document.readyState === 'interactive') { + main() + } else { + document.addEventListener('DOMContentLoaded', main) + } +}()) diff --git a/config.rb b/config.rb index af89574..4b97be5 100644 --- a/config.rb +++ b/config.rb @@ -67,7 +67,16 @@ source: ".build/js", latency: 2 +%w(vertical horizontal).each do |direction| + %w(small medium large).each do |size| + proxy "/scrollerful/demo/#{direction}/#{size}.html", + '/scrollerful/demo/demo.html', + locals: { direction: direction, size: size } + end +end + ignore '.DS_Store' +ignore '/scrollerful/demo/demo.html' page '/*.json', layout: false page '/*.txt', layout: false diff --git a/dist/scrollerful-auto.min.js b/dist/scrollerful-auto.min.js index b0af905..7520ec5 100644 --- a/dist/scrollerful-auto.min.js +++ b/dist/scrollerful-auto.min.js @@ -1,2 +1,2 @@ /*! scrollerful v0.5.1 | (c) 2022-2023 Rémino Rem | ISC Licence */ -(function(f){typeof define==='function'&&define.amd?define(f):f();})((function(){'use strict';var e$1 = ":root{--scrollerful-delay:0s}@media screen{.scrollerful--enabled .scrollerful{min-height:100%}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--snap,.scrollerful--enabled .scrollerful--snap--page,.scrollerful--enabled .scrollerful--snap--page body{scroll-snap-stop:always;scroll-snap-type:y proximity}}.scrollerful--enabled .scrollerful--snap,.scrollerful--enabled .scrollerful--snap--page{overflow-y:auto}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--snap .scrollerful__tray,.scrollerful--enabled .scrollerful--snap--page .scrollerful__tray{scroll-snap-align:start end}}.scrollerful--enabled .scrollerful--snap{height:100%}.scrollerful--enabled .scrollerful--x{display:flex;flex-flow:row nowrap}.scrollerful--enabled .scrollerful--x.scrollerful--snap{overflow-x:auto;overflow-y:hidden}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--x.scrollerful--snap{scroll-snap-type:x proximity}}.scrollerful--enabled .scrollerful--x .scrollerful__plate{left:0;max-height:none;max-width:100%;top:auto;width:100vw;width:100lvw}.scrollerful--enabled .scrollerful--x .scrollerful__tray{flex-shrink:0;height:auto;width:300vw;width:300lvw}.scrollerful--enabled .scrollerful--x .scrollerful__tray--padding{height:auto;width:100vw;width:100lvw}.scrollerful--enabled .scrollerful__ruler{background:none transparent;border:none;bottom:0;display:block;height:100vh;height:100lvh;left:-200%;pointer-events:none;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100vw;width:100lvw;z-index:-10}.scrollerful--enabled .scrollerful__plate{align-items:center;display:flex;flex-flow:column;height:100vh;height:100lvh;justify-content:center;max-height:100%;overflow:hidden;position:sticky;top:0}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--inner,.scrollerful--enabled .scrollerful__sprite--outer{animation-duration:100s;animation-fill-mode:both;animation-play-state:paused;animation-timing-function:linear}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--inner{animation-delay:calc(var(--scrollerful-progress-inner, 0)*-100s + var(--scrollerful-delay, 0))}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--outer{animation-delay:calc(var(--scrollerful-progress-outer, 0)*-100s + var(--scrollerful-delay, 0))}.scrollerful--enabled .scrollerful__tray{height:300vh;height:300lvh;position:relative}.scrollerful--enabled .scrollerful__tray--padding{height:100vh;height:100lvh}}";const t="scrollerful",n=`${t}--enabled`,r=`${t}--x`,s=`${t}--inside--inner`,o=`${t}--inside--outer`,i=`${t}__ruler`,l=`--${t}-progress-inner`,a=`--${t}-progress-outer`,c=`${t}innerenter`,d=`${t}innerexit`,u=`${t}outerenter`,m=`${t}outerexit`,g=`${t}scroll`,p=`.${t}`,y=`.${t}__tray`,E=`${t}_ruler`,b=`${t}_style`;let v;const h=(e,t=!1)=>t?e.scrollWidth:e.scrollHeight,$=e=>document.getElementById(E).getBoundingClientRect()[e?"width":"height"],L=(e,t,n)=>{const[r,s]=((...e)=>e.sort(((e,t)=>e-t)))(t,n);return e>=r&&e<=s},f=async(e,t)=>{const n=((e,t)=>{const{containerStart:n,containerSize:r,viewSize:s}=((e,t)=>{const{size:n,start:r}=((e,t=!1)=>{if(t){const{left:t,width:n}=e.getBoundingClientRect();return {size:n,start:t}}const{height:n,top:r}=e.getBoundingClientRect();return {size:n,start:r}})(e,t),s=((e,t)=>["auto","scroll"].includes(getComputedStyle(e).getPropertyValue("overflow-"+(t?"x":"y"))))(e,t);return {containerStart:r,containerSize:s?h(e,t):n,viewSize:s?n:$(t)}})(e,t);return {inner:n/-(r-s),outer:(n-s)/-(r+s)}})(e,t);e.dispatchEvent(new CustomEvent(g,{detail:{progress:n},bubbles:!0,cancelable:!0,composed:!1}));},w=({target:e,detail:{progress:{inner:t,outer:n}}})=>{var r;L(n,0,1)?(e.style.setProperty(l,t),e.style.setProperty(a,n)):(r=e,[l,a].forEach((e=>r.style.removeProperty(e))));},C=(e,t,n,r,s)=>{L(t,0,1)?e.classList.contains(s)||(e.classList.add(s),e.dispatchEvent(new CustomEvent(n,{bubbles:!0,cancelable:!0,composed:!1}))):e.classList.contains(s)&&(e.classList.remove(s),e.dispatchEvent(new CustomEvent(r,{bubbles:!0,cancelable:!0,composed:!1})));},S=({target:e,detail:{progress:{inner:t}}})=>{C(e,t,c,d,s);},z=({target:e,detail:{progress:{outer:t}}})=>{C(e,t,u,m,o);},A=({target:e})=>{v&&cancelAnimationFrame(v),v=requestAnimationFrame((()=>{(async e=>{const t=e.classList.contains(r);Promise.all([e,...e.querySelectorAll(y)].map((e=>f(e,t))));})(e),v=null;}));},x=e=>{[e,...e.querySelectorAll(y)].forEach((e=>{e.addEventListener(g,w),e.addEventListener(g,z),e.addEventListener(g,S);}));};var e = ()=>{(()=>{if(document.getElementById(b))return;const t=document.createElement("style");t.setAttribute("id",b),t.textContent=e$1,document.head.appendChild(t);})(),(()=>{const e=document.createElement("div");e.setAttribute("id",E),e.classList.add(i),document.body.appendChild(e);})(),Array.from(document.querySelectorAll(p)).forEach((e=>{e.addEventListener("resize",A),e.addEventListener("scroll",A),x(e),A({target:e});})),window.addEventListener("resize",(()=>A({target:document.body}))),window.addEventListener("scroll",(()=>A({target:document.body}))),x(document.body),A({target:document.body}),document.documentElement.classList.add(n);};"interactive"===document.readyState?e():document.addEventListener("DOMContentLoaded",e);})); \ No newline at end of file +(function(f){typeof define==='function'&&define.amd?define(f):f();})((function(){'use strict';var e = ":root{--scrollerful-delay:0s}@media screen{.scrollerful--enabled .scrollerful{min-height:100%}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--snap,.scrollerful--enabled .scrollerful--snap--page,.scrollerful--enabled .scrollerful--snap--page body{scroll-snap-stop:always;scroll-snap-type:y proximity}}.scrollerful--enabled .scrollerful--snap,.scrollerful--enabled .scrollerful--snap--page{overflow-y:auto}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--snap .scrollerful__tray,.scrollerful--enabled .scrollerful--snap--page .scrollerful__tray{scroll-snap-align:start end}}.scrollerful--enabled .scrollerful--snap{height:100%}.scrollerful--enabled .scrollerful--x{display:flex;flex-flow:row nowrap}.scrollerful--enabled .scrollerful--x.scrollerful--snap{overflow-x:auto;overflow-y:hidden}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--x.scrollerful--snap{scroll-snap-type:x proximity}}.scrollerful--enabled .scrollerful--x .scrollerful__plate{left:0;max-height:none;max-width:100%;top:auto;width:100vw;width:100lvw}.scrollerful--enabled .scrollerful--x .scrollerful__tray{flex-shrink:0;height:auto;width:300vw;width:300lvw}.scrollerful--enabled .scrollerful--x .scrollerful__tray--padding{height:auto;width:100vw;width:100lvw}.scrollerful--enabled .scrollerful__ruler{background:none transparent;border:none;bottom:0;display:block;height:100vh;height:100lvh;left:-200%;pointer-events:none;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100vw;width:100lvw;z-index:-10}.scrollerful--enabled .scrollerful__plate{align-items:center;display:flex;flex-flow:column;height:100vh;height:100lvh;justify-content:center;max-height:100%;overflow:hidden;position:sticky;top:0}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--inner,.scrollerful--enabled .scrollerful__sprite--outer{animation-duration:100s;animation-fill-mode:both;animation-play-state:paused;animation-timing-function:linear}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--inner{animation-delay:calc(var(--scrollerful-progress-inner, 0)*-100s + var(--scrollerful-delay, 0))}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--outer{animation-delay:calc(var(--scrollerful-progress-outer, 0)*-100s + var(--scrollerful-delay, 0))}.scrollerful--enabled .scrollerful__tray{height:300vh;height:300lvh;position:relative}.scrollerful--enabled .scrollerful__tray--padding{height:100vh;height:100lvh}}";const calcInnerProgress=(r,t,e)=>{if(t===e){const t=(r-e)/e*-1;switch(!0){case r<0:return t;case r>0:return t-1;default:return .5}}const c=r/(t-e)*-1;return 1==t(r-e)/(e+t)*-1;const n="scrollerful",s=`${n}--enabled`,o=`${n}--x`,i=`${n}--inside--inner`,l=`${n}--inside--outer`,a=`${n}__ruler`,c=`--${n}-progress-inner`,d=`--${n}-progress-outer`,u=`${n}innerenter`,m=`${n}innerexit`,g=`${n}outerenter`,p=`${n}outerexit`,y=`${n}scroll`,E=`.${n}`,b=`.${n}__tray`,v=`${n}_ruler`,h=`${n}_style`;let $;const L=(e,t=!1)=>t?e.scrollWidth:e.scrollHeight,f=e=>document.getElementById(v).getBoundingClientRect()[e?"width":"height"],w=(e,t,r)=>{const[n,s]=((...e)=>e.sort(((e,t)=>e-t)))(t,r);return e>=n&&e<=s},C=async(e,n)=>{const s=((e,n)=>{const{containerStart:s,containerSize:o,viewSize:i}=((e,t)=>{const{size:r,start:n}=((e,t=!1)=>{if(t){const{left:t,width:r}=e.getBoundingClientRect();return {size:r,start:t}}const{height:r,top:n}=e.getBoundingClientRect();return {size:r,start:n}})(e,t),s=((e,t)=>["auto","scroll"].includes(getComputedStyle(e).getPropertyValue("overflow-"+(t?"x":"y"))))(e,t);return {containerStart:n,containerSize:s?L(e,t):r,viewSize:s?r:f(t)}})(e,n);return {inner:calcInnerProgress(s,o,i),outer:calcOuterProgress(s,o,i)}})(e,n);e.dispatchEvent(new CustomEvent(y,{detail:{progress:s},bubbles:!0,cancelable:!0,composed:!1}));},S=({target:e,detail:{progress:{inner:t,outer:r}}})=>{var n;w(r,0,1)?(e.style.setProperty(c,t),e.style.setProperty(d,r)):(n=e,[c,d].forEach((e=>n.style.removeProperty(e))));},z=(e,t,r,n,s)=>{w(t,0,1)?e.classList.contains(s)||(e.classList.add(s),e.dispatchEvent(new CustomEvent(r,{bubbles:!0,cancelable:!0,composed:!1}))):e.classList.contains(s)&&(e.classList.remove(s),e.dispatchEvent(new CustomEvent(n,{bubbles:!0,cancelable:!0,composed:!1})));},A=({target:e,detail:{progress:{inner:t}}})=>{z(e,t,u,m,i);},x=({target:e,detail:{progress:{outer:t}}})=>{z(e,t,g,p,l);},_=({target:e})=>{$&&cancelAnimationFrame($),$=requestAnimationFrame((()=>{(async e=>{const t=e.classList.contains(o);Promise.all([e,...e.querySelectorAll(b)].map((e=>C(e,t))));})(e),$=null;}));},B=e=>{[e,...e.querySelectorAll(b)].forEach((e=>{e.addEventListener(y,S),e.addEventListener(y,x),e.addEventListener(y,A);}));};var m$1 = ()=>{(()=>{if(document.getElementById(h))return;const t=document.createElement("style");t.setAttribute("id",h),t.textContent=e,document.head.appendChild(t);})(),(()=>{const e=document.createElement("div");e.setAttribute("id",v),e.classList.add(a),document.body.appendChild(e);})(),Array.from(document.querySelectorAll(E)).forEach((e=>{e.addEventListener("resize",_),e.addEventListener("scroll",_),B(e),_({target:e});})),window.addEventListener("resize",(()=>_({target:document.body}))),window.addEventListener("scroll",(()=>_({target:document.body}))),B(document.body),_({target:document.body}),document.documentElement.classList.add(s);};"interactive"===document.readyState?m$1():document.addEventListener("DOMContentLoaded",m$1);})); \ No newline at end of file diff --git a/dist/scrollerful.cjs b/dist/scrollerful.cjs index a9460ad..80d189c 100644 --- a/dist/scrollerful.cjs +++ b/dist/scrollerful.cjs @@ -7,6 +7,29 @@ var style = ":root{--scrollerful-delay:0s}@media screen{.scrollerful--enabled .scrollerful{min-height:100%}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--snap,.scrollerful--enabled .scrollerful--snap--page,.scrollerful--enabled .scrollerful--snap--page body{scroll-snap-stop:always;scroll-snap-type:y proximity}}.scrollerful--enabled .scrollerful--snap,.scrollerful--enabled .scrollerful--snap--page{overflow-y:auto}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--snap .scrollerful__tray,.scrollerful--enabled .scrollerful--snap--page .scrollerful__tray{scroll-snap-align:start end}}.scrollerful--enabled .scrollerful--snap{height:100%}.scrollerful--enabled .scrollerful--x{display:flex;flex-flow:row nowrap}.scrollerful--enabled .scrollerful--x.scrollerful--snap{overflow-x:auto;overflow-y:hidden}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--x.scrollerful--snap{scroll-snap-type:x proximity}}.scrollerful--enabled .scrollerful--x .scrollerful__plate{left:0;max-height:none;max-width:100%;top:auto;width:100vw;width:100lvw}.scrollerful--enabled .scrollerful--x .scrollerful__tray{flex-shrink:0;height:auto;width:300vw;width:300lvw}.scrollerful--enabled .scrollerful--x .scrollerful__tray--padding{height:auto;width:100vw;width:100lvw}.scrollerful--enabled .scrollerful__ruler{background:none transparent;border:none;bottom:0;display:block;height:100vh;height:100lvh;left:-200%;pointer-events:none;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100vw;width:100lvw;z-index:-10}.scrollerful--enabled .scrollerful__plate{align-items:center;display:flex;flex-flow:column;height:100vh;height:100lvh;justify-content:center;max-height:100%;overflow:hidden;position:sticky;top:0}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--inner,.scrollerful--enabled .scrollerful__sprite--outer{animation-duration:100s;animation-fill-mode:both;animation-play-state:paused;animation-timing-function:linear}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--inner{animation-delay:calc(var(--scrollerful-progress-inner, 0)*-100s + var(--scrollerful-delay, 0))}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--outer{animation-delay:calc(var(--scrollerful-progress-outer, 0)*-100s + var(--scrollerful-delay, 0))}.scrollerful--enabled .scrollerful__tray{height:300vh;height:300lvh;position:relative}.scrollerful--enabled .scrollerful__tray--padding{height:100vh;height:100lvh}}"; + const calcInnerProgress = (containerStart, containerSize, viewSize) => { + if (containerSize === viewSize) { + const progress = (((containerStart - viewSize) / (viewSize)) * -1); + + switch (true) { + case containerStart < 0: return progress + case containerStart > 0: return progress - 1 + default: return 0.5 + } + } + + const progress = (((containerStart) / (containerSize - viewSize)) * -1); + + switch (true) { + case containerSize < viewSize: return 1 - progress + default: return progress + } + }; + + const calcOuterProgress = (containerStart, containerSize, viewSize) => ( + ((containerStart - viewSize) / (viewSize + containerSize)) * -1 + ); + const SCRIPT_NAME = 'scrollerful'; const CSS_CLASS_ENABLED = `${SCRIPT_NAME}--enabled`; @@ -87,8 +110,8 @@ const { containerStart, containerSize, viewSize } = getContainerCoords(el, horizontal); return { - inner: containerStart / -(containerSize - viewSize), - outer: (containerStart - viewSize) / -(containerSize + viewSize), + inner: calcInnerProgress(containerStart, containerSize, viewSize), + outer: calcOuterProgress(containerStart, containerSize, viewSize), } }; diff --git a/dist/scrollerful.min.js b/dist/scrollerful.min.js index 89959cd..d0d996d 100644 --- a/dist/scrollerful.min.js +++ b/dist/scrollerful.min.js @@ -1,2 +1,2 @@ /*! scrollerful v0.5.1 | (c) 2022-2023 Rémino Rem | ISC Licence */ -(function(g,f){typeof exports==='object'&&typeof module!=='undefined'?module.exports=f():typeof define==='function'&&define.amd?define(f):(g=typeof globalThis!=='undefined'?globalThis:g||self,g.scrollerful=f());})(this,(function(){'use strict';var e = ":root{--scrollerful-delay:0s}@media screen{.scrollerful--enabled .scrollerful{min-height:100%}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--snap,.scrollerful--enabled .scrollerful--snap--page,.scrollerful--enabled .scrollerful--snap--page body{scroll-snap-stop:always;scroll-snap-type:y proximity}}.scrollerful--enabled .scrollerful--snap,.scrollerful--enabled .scrollerful--snap--page{overflow-y:auto}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--snap .scrollerful__tray,.scrollerful--enabled .scrollerful--snap--page .scrollerful__tray{scroll-snap-align:start end}}.scrollerful--enabled .scrollerful--snap{height:100%}.scrollerful--enabled .scrollerful--x{display:flex;flex-flow:row nowrap}.scrollerful--enabled .scrollerful--x.scrollerful--snap{overflow-x:auto;overflow-y:hidden}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--x.scrollerful--snap{scroll-snap-type:x proximity}}.scrollerful--enabled .scrollerful--x .scrollerful__plate{left:0;max-height:none;max-width:100%;top:auto;width:100vw;width:100lvw}.scrollerful--enabled .scrollerful--x .scrollerful__tray{flex-shrink:0;height:auto;width:300vw;width:300lvw}.scrollerful--enabled .scrollerful--x .scrollerful__tray--padding{height:auto;width:100vw;width:100lvw}.scrollerful--enabled .scrollerful__ruler{background:none transparent;border:none;bottom:0;display:block;height:100vh;height:100lvh;left:-200%;pointer-events:none;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100vw;width:100lvw;z-index:-10}.scrollerful--enabled .scrollerful__plate{align-items:center;display:flex;flex-flow:column;height:100vh;height:100lvh;justify-content:center;max-height:100%;overflow:hidden;position:sticky;top:0}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--inner,.scrollerful--enabled .scrollerful__sprite--outer{animation-duration:100s;animation-fill-mode:both;animation-play-state:paused;animation-timing-function:linear}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--inner{animation-delay:calc(var(--scrollerful-progress-inner, 0)*-100s + var(--scrollerful-delay, 0))}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--outer{animation-delay:calc(var(--scrollerful-progress-outer, 0)*-100s + var(--scrollerful-delay, 0))}.scrollerful--enabled .scrollerful__tray{height:300vh;height:300lvh;position:relative}.scrollerful--enabled .scrollerful__tray--padding{height:100vh;height:100lvh}}";const t="scrollerful",n=`${t}--enabled`,r=`${t}--x`,s=`${t}--inside--inner`,o=`${t}--inside--outer`,i=`${t}__ruler`,l=`--${t}-progress-inner`,a=`--${t}-progress-outer`,c=`${t}innerenter`,d=`${t}innerexit`,u=`${t}outerenter`,m=`${t}outerexit`,g=`${t}scroll`,p=`.${t}`,y=`.${t}__tray`,E=`${t}_ruler`,b=`${t}_style`;let v;const h=(e,t=!1)=>t?e.scrollWidth:e.scrollHeight,$=e=>document.getElementById(E).getBoundingClientRect()[e?"width":"height"],L=(e,t,n)=>{const[r,s]=((...e)=>e.sort(((e,t)=>e-t)))(t,n);return e>=r&&e<=s},f=async(e,t)=>{const n=((e,t)=>{const{containerStart:n,containerSize:r,viewSize:s}=((e,t)=>{const{size:n,start:r}=((e,t=!1)=>{if(t){const{left:t,width:n}=e.getBoundingClientRect();return {size:n,start:t}}const{height:n,top:r}=e.getBoundingClientRect();return {size:n,start:r}})(e,t),s=((e,t)=>["auto","scroll"].includes(getComputedStyle(e).getPropertyValue("overflow-"+(t?"x":"y"))))(e,t);return {containerStart:r,containerSize:s?h(e,t):n,viewSize:s?n:$(t)}})(e,t);return {inner:n/-(r-s),outer:(n-s)/-(r+s)}})(e,t);e.dispatchEvent(new CustomEvent(g,{detail:{progress:n},bubbles:!0,cancelable:!0,composed:!1}));},w=({target:e,detail:{progress:{inner:t,outer:n}}})=>{var r;L(n,0,1)?(e.style.setProperty(l,t),e.style.setProperty(a,n)):(r=e,[l,a].forEach((e=>r.style.removeProperty(e))));},C=(e,t,n,r,s)=>{L(t,0,1)?e.classList.contains(s)||(e.classList.add(s),e.dispatchEvent(new CustomEvent(n,{bubbles:!0,cancelable:!0,composed:!1}))):e.classList.contains(s)&&(e.classList.remove(s),e.dispatchEvent(new CustomEvent(r,{bubbles:!0,cancelable:!0,composed:!1})));},S=({target:e,detail:{progress:{inner:t}}})=>{C(e,t,c,d,s);},z=({target:e,detail:{progress:{outer:t}}})=>{C(e,t,u,m,o);},A=({target:e})=>{v&&cancelAnimationFrame(v),v=requestAnimationFrame((()=>{(async e=>{const t=e.classList.contains(r);Promise.all([e,...e.querySelectorAll(y)].map((e=>f(e,t))));})(e),v=null;}));},x=e=>{[e,...e.querySelectorAll(y)].forEach((e=>{e.addEventListener(g,w),e.addEventListener(g,z),e.addEventListener(g,S);}));};var scrollerful = ()=>{(()=>{if(document.getElementById(b))return;const t=document.createElement("style");t.setAttribute("id",b),t.textContent=e,document.head.appendChild(t);})(),(()=>{const e=document.createElement("div");e.setAttribute("id",E),e.classList.add(i),document.body.appendChild(e);})(),Array.from(document.querySelectorAll(p)).forEach((e=>{e.addEventListener("resize",A),e.addEventListener("scroll",A),x(e),A({target:e});})),window.addEventListener("resize",(()=>A({target:document.body}))),window.addEventListener("scroll",(()=>A({target:document.body}))),x(document.body),A({target:document.body}),document.documentElement.classList.add(n);};return scrollerful;})); \ No newline at end of file +(function(g,f){typeof exports==='object'&&typeof module!=='undefined'?module.exports=f():typeof define==='function'&&define.amd?define(f):(g=typeof globalThis!=='undefined'?globalThis:g||self,g.scrollerful=f());})(this,(function(){'use strict';var e = ":root{--scrollerful-delay:0s}@media screen{.scrollerful--enabled .scrollerful{min-height:100%}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--snap,.scrollerful--enabled .scrollerful--snap--page,.scrollerful--enabled .scrollerful--snap--page body{scroll-snap-stop:always;scroll-snap-type:y proximity}}.scrollerful--enabled .scrollerful--snap,.scrollerful--enabled .scrollerful--snap--page{overflow-y:auto}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--snap .scrollerful__tray,.scrollerful--enabled .scrollerful--snap--page .scrollerful__tray{scroll-snap-align:start end}}.scrollerful--enabled .scrollerful--snap{height:100%}.scrollerful--enabled .scrollerful--x{display:flex;flex-flow:row nowrap}.scrollerful--enabled .scrollerful--x.scrollerful--snap{overflow-x:auto;overflow-y:hidden}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--x.scrollerful--snap{scroll-snap-type:x proximity}}.scrollerful--enabled .scrollerful--x .scrollerful__plate{left:0;max-height:none;max-width:100%;top:auto;width:100vw;width:100lvw}.scrollerful--enabled .scrollerful--x .scrollerful__tray{flex-shrink:0;height:auto;width:300vw;width:300lvw}.scrollerful--enabled .scrollerful--x .scrollerful__tray--padding{height:auto;width:100vw;width:100lvw}.scrollerful--enabled .scrollerful__ruler{background:none transparent;border:none;bottom:0;display:block;height:100vh;height:100lvh;left:-200%;pointer-events:none;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100vw;width:100lvw;z-index:-10}.scrollerful--enabled .scrollerful__plate{align-items:center;display:flex;flex-flow:column;height:100vh;height:100lvh;justify-content:center;max-height:100%;overflow:hidden;position:sticky;top:0}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--inner,.scrollerful--enabled .scrollerful__sprite--outer{animation-duration:100s;animation-fill-mode:both;animation-play-state:paused;animation-timing-function:linear}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--inner{animation-delay:calc(var(--scrollerful-progress-inner, 0)*-100s + var(--scrollerful-delay, 0))}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--outer{animation-delay:calc(var(--scrollerful-progress-outer, 0)*-100s + var(--scrollerful-delay, 0))}.scrollerful--enabled .scrollerful__tray{height:300vh;height:300lvh;position:relative}.scrollerful--enabled .scrollerful__tray--padding{height:100vh;height:100lvh}}";const calcInnerProgress=(r,t,e)=>{if(t===e){const t=(r-e)/e*-1;switch(!0){case r<0:return t;case r>0:return t-1;default:return .5}}const c=r/(t-e)*-1;return 1==t(r-e)/(e+t)*-1;const n="scrollerful",s=`${n}--enabled`,o=`${n}--x`,i=`${n}--inside--inner`,l=`${n}--inside--outer`,a=`${n}__ruler`,c=`--${n}-progress-inner`,d=`--${n}-progress-outer`,u=`${n}innerenter`,m=`${n}innerexit`,g=`${n}outerenter`,p=`${n}outerexit`,y=`${n}scroll`,E=`.${n}`,b=`.${n}__tray`,v=`${n}_ruler`,h=`${n}_style`;let $;const L=(e,t=!1)=>t?e.scrollWidth:e.scrollHeight,f=e=>document.getElementById(v).getBoundingClientRect()[e?"width":"height"],w=(e,t,r)=>{const[n,s]=((...e)=>e.sort(((e,t)=>e-t)))(t,r);return e>=n&&e<=s},C=async(e,n)=>{const s=((e,n)=>{const{containerStart:s,containerSize:o,viewSize:i}=((e,t)=>{const{size:r,start:n}=((e,t=!1)=>{if(t){const{left:t,width:r}=e.getBoundingClientRect();return {size:r,start:t}}const{height:r,top:n}=e.getBoundingClientRect();return {size:r,start:n}})(e,t),s=((e,t)=>["auto","scroll"].includes(getComputedStyle(e).getPropertyValue("overflow-"+(t?"x":"y"))))(e,t);return {containerStart:n,containerSize:s?L(e,t):r,viewSize:s?r:f(t)}})(e,n);return {inner:calcInnerProgress(s,o,i),outer:calcOuterProgress(s,o,i)}})(e,n);e.dispatchEvent(new CustomEvent(y,{detail:{progress:s},bubbles:!0,cancelable:!0,composed:!1}));},S=({target:e,detail:{progress:{inner:t,outer:r}}})=>{var n;w(r,0,1)?(e.style.setProperty(c,t),e.style.setProperty(d,r)):(n=e,[c,d].forEach((e=>n.style.removeProperty(e))));},z=(e,t,r,n,s)=>{w(t,0,1)?e.classList.contains(s)||(e.classList.add(s),e.dispatchEvent(new CustomEvent(r,{bubbles:!0,cancelable:!0,composed:!1}))):e.classList.contains(s)&&(e.classList.remove(s),e.dispatchEvent(new CustomEvent(n,{bubbles:!0,cancelable:!0,composed:!1})));},A=({target:e,detail:{progress:{inner:t}}})=>{z(e,t,u,m,i);},x=({target:e,detail:{progress:{outer:t}}})=>{z(e,t,g,p,l);},_=({target:e})=>{$&&cancelAnimationFrame($),$=requestAnimationFrame((()=>{(async e=>{const t=e.classList.contains(o);Promise.all([e,...e.querySelectorAll(b)].map((e=>C(e,t))));})(e),$=null;}));},B=e=>{[e,...e.querySelectorAll(b)].forEach((e=>{e.addEventListener(y,S),e.addEventListener(y,x),e.addEventListener(y,A);}));};var m$1 = ()=>{(()=>{if(document.getElementById(h))return;const t=document.createElement("style");t.setAttribute("id",h),t.textContent=e,document.head.appendChild(t);})(),(()=>{const e=document.createElement("div");e.setAttribute("id",v),e.classList.add(a),document.body.appendChild(e);})(),Array.from(document.querySelectorAll(E)).forEach((e=>{e.addEventListener("resize",_),e.addEventListener("scroll",_),B(e),_({target:e});})),window.addEventListener("resize",(()=>_({target:document.body}))),window.addEventListener("scroll",(()=>_({target:document.body}))),B(document.body),_({target:document.body}),document.documentElement.classList.add(s);};return m$1;})); \ No newline at end of file diff --git a/dist/scrollerful.min.mjs b/dist/scrollerful.min.mjs index 7b3ec34..d930ad3 100644 --- a/dist/scrollerful.min.mjs +++ b/dist/scrollerful.min.mjs @@ -1,2 +1,2 @@ /*! scrollerful v0.5.1 | (c) 2022-2023 Rémino Rem | ISC Licence */ -var e = ":root{--scrollerful-delay:0s}@media screen{.scrollerful--enabled .scrollerful{min-height:100%}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--snap,.scrollerful--enabled .scrollerful--snap--page,.scrollerful--enabled .scrollerful--snap--page body{scroll-snap-stop:always;scroll-snap-type:y proximity}}.scrollerful--enabled .scrollerful--snap,.scrollerful--enabled .scrollerful--snap--page{overflow-y:auto}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--snap .scrollerful__tray,.scrollerful--enabled .scrollerful--snap--page .scrollerful__tray{scroll-snap-align:start end}}.scrollerful--enabled .scrollerful--snap{height:100%}.scrollerful--enabled .scrollerful--x{display:flex;flex-flow:row nowrap}.scrollerful--enabled .scrollerful--x.scrollerful--snap{overflow-x:auto;overflow-y:hidden}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--x.scrollerful--snap{scroll-snap-type:x proximity}}.scrollerful--enabled .scrollerful--x .scrollerful__plate{left:0;max-height:none;max-width:100%;top:auto;width:100vw;width:100lvw}.scrollerful--enabled .scrollerful--x .scrollerful__tray{flex-shrink:0;height:auto;width:300vw;width:300lvw}.scrollerful--enabled .scrollerful--x .scrollerful__tray--padding{height:auto;width:100vw;width:100lvw}.scrollerful--enabled .scrollerful__ruler{background:none transparent;border:none;bottom:0;display:block;height:100vh;height:100lvh;left:-200%;pointer-events:none;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100vw;width:100lvw;z-index:-10}.scrollerful--enabled .scrollerful__plate{align-items:center;display:flex;flex-flow:column;height:100vh;height:100lvh;justify-content:center;max-height:100%;overflow:hidden;position:sticky;top:0}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--inner,.scrollerful--enabled .scrollerful__sprite--outer{animation-duration:100s;animation-fill-mode:both;animation-play-state:paused;animation-timing-function:linear}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--inner{animation-delay:calc(var(--scrollerful-progress-inner, 0)*-100s + var(--scrollerful-delay, 0))}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--outer{animation-delay:calc(var(--scrollerful-progress-outer, 0)*-100s + var(--scrollerful-delay, 0))}.scrollerful--enabled .scrollerful__tray{height:300vh;height:300lvh;position:relative}.scrollerful--enabled .scrollerful__tray--padding{height:100vh;height:100lvh}}";const t="scrollerful",n=`${t}--enabled`,r=`${t}--x`,s=`${t}--inside--inner`,o=`${t}--inside--outer`,i=`${t}__ruler`,l=`--${t}-progress-inner`,a=`--${t}-progress-outer`,c=`${t}innerenter`,d=`${t}innerexit`,u=`${t}outerenter`,m=`${t}outerexit`,g=`${t}scroll`,p=`.${t}`,y=`.${t}__tray`,E=`${t}_ruler`,b=`${t}_style`;let v;const h=(e,t=!1)=>t?e.scrollWidth:e.scrollHeight,$=e=>document.getElementById(E).getBoundingClientRect()[e?"width":"height"],L=(e,t,n)=>{const[r,s]=((...e)=>e.sort(((e,t)=>e-t)))(t,n);return e>=r&&e<=s},f=async(e,t)=>{const n=((e,t)=>{const{containerStart:n,containerSize:r,viewSize:s}=((e,t)=>{const{size:n,start:r}=((e,t=!1)=>{if(t){const{left:t,width:n}=e.getBoundingClientRect();return {size:n,start:t}}const{height:n,top:r}=e.getBoundingClientRect();return {size:n,start:r}})(e,t),s=((e,t)=>["auto","scroll"].includes(getComputedStyle(e).getPropertyValue("overflow-"+(t?"x":"y"))))(e,t);return {containerStart:r,containerSize:s?h(e,t):n,viewSize:s?n:$(t)}})(e,t);return {inner:n/-(r-s),outer:(n-s)/-(r+s)}})(e,t);e.dispatchEvent(new CustomEvent(g,{detail:{progress:n},bubbles:!0,cancelable:!0,composed:!1}));},w=({target:e,detail:{progress:{inner:t,outer:n}}})=>{var r;L(n,0,1)?(e.style.setProperty(l,t),e.style.setProperty(a,n)):(r=e,[l,a].forEach((e=>r.style.removeProperty(e))));},C=(e,t,n,r,s)=>{L(t,0,1)?e.classList.contains(s)||(e.classList.add(s),e.dispatchEvent(new CustomEvent(n,{bubbles:!0,cancelable:!0,composed:!1}))):e.classList.contains(s)&&(e.classList.remove(s),e.dispatchEvent(new CustomEvent(r,{bubbles:!0,cancelable:!0,composed:!1})));},S=({target:e,detail:{progress:{inner:t}}})=>{C(e,t,c,d,s);},z=({target:e,detail:{progress:{outer:t}}})=>{C(e,t,u,m,o);},A=({target:e})=>{v&&cancelAnimationFrame(v),v=requestAnimationFrame((()=>{(async e=>{const t=e.classList.contains(r);Promise.all([e,...e.querySelectorAll(y)].map((e=>f(e,t))));})(e),v=null;}));},x=e=>{[e,...e.querySelectorAll(y)].forEach((e=>{e.addEventListener(g,w),e.addEventListener(g,z),e.addEventListener(g,S);}));};var scrollerful = ()=>{(()=>{if(document.getElementById(b))return;const t=document.createElement("style");t.setAttribute("id",b),t.textContent=e,document.head.appendChild(t);})(),(()=>{const e=document.createElement("div");e.setAttribute("id",E),e.classList.add(i),document.body.appendChild(e);})(),Array.from(document.querySelectorAll(p)).forEach((e=>{e.addEventListener("resize",A),e.addEventListener("scroll",A),x(e),A({target:e});})),window.addEventListener("resize",(()=>A({target:document.body}))),window.addEventListener("scroll",(()=>A({target:document.body}))),x(document.body),A({target:document.body}),document.documentElement.classList.add(n);};export{scrollerful as default}; \ No newline at end of file +var e = ":root{--scrollerful-delay:0s}@media screen{.scrollerful--enabled .scrollerful{min-height:100%}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--snap,.scrollerful--enabled .scrollerful--snap--page,.scrollerful--enabled .scrollerful--snap--page body{scroll-snap-stop:always;scroll-snap-type:y proximity}}.scrollerful--enabled .scrollerful--snap,.scrollerful--enabled .scrollerful--snap--page{overflow-y:auto}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--snap .scrollerful__tray,.scrollerful--enabled .scrollerful--snap--page .scrollerful__tray{scroll-snap-align:start end}}.scrollerful--enabled .scrollerful--snap{height:100%}.scrollerful--enabled .scrollerful--x{display:flex;flex-flow:row nowrap}.scrollerful--enabled .scrollerful--x.scrollerful--snap{overflow-x:auto;overflow-y:hidden}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--x.scrollerful--snap{scroll-snap-type:x proximity}}.scrollerful--enabled .scrollerful--x .scrollerful__plate{left:0;max-height:none;max-width:100%;top:auto;width:100vw;width:100lvw}.scrollerful--enabled .scrollerful--x .scrollerful__tray{flex-shrink:0;height:auto;width:300vw;width:300lvw}.scrollerful--enabled .scrollerful--x .scrollerful__tray--padding{height:auto;width:100vw;width:100lvw}.scrollerful--enabled .scrollerful__ruler{background:none transparent;border:none;bottom:0;display:block;height:100vh;height:100lvh;left:-200%;pointer-events:none;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100vw;width:100lvw;z-index:-10}.scrollerful--enabled .scrollerful__plate{align-items:center;display:flex;flex-flow:column;height:100vh;height:100lvh;justify-content:center;max-height:100%;overflow:hidden;position:sticky;top:0}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--inner,.scrollerful--enabled .scrollerful__sprite--outer{animation-duration:100s;animation-fill-mode:both;animation-play-state:paused;animation-timing-function:linear}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--inner{animation-delay:calc(var(--scrollerful-progress-inner, 0)*-100s + var(--scrollerful-delay, 0))}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--outer{animation-delay:calc(var(--scrollerful-progress-outer, 0)*-100s + var(--scrollerful-delay, 0))}.scrollerful--enabled .scrollerful__tray{height:300vh;height:300lvh;position:relative}.scrollerful--enabled .scrollerful__tray--padding{height:100vh;height:100lvh}}";const calcInnerProgress=(r,t,e)=>{if(t===e){const t=(r-e)/e*-1;switch(!0){case r<0:return t;case r>0:return t-1;default:return .5}}const c=r/(t-e)*-1;return 1==t(r-e)/(e+t)*-1;const n="scrollerful",s=`${n}--enabled`,o=`${n}--x`,i=`${n}--inside--inner`,l=`${n}--inside--outer`,a=`${n}__ruler`,c=`--${n}-progress-inner`,d=`--${n}-progress-outer`,u=`${n}innerenter`,m=`${n}innerexit`,g=`${n}outerenter`,p=`${n}outerexit`,y=`${n}scroll`,E=`.${n}`,b=`.${n}__tray`,v=`${n}_ruler`,h=`${n}_style`;let $;const L=(e,t=!1)=>t?e.scrollWidth:e.scrollHeight,f=e=>document.getElementById(v).getBoundingClientRect()[e?"width":"height"],w=(e,t,r)=>{const[n,s]=((...e)=>e.sort(((e,t)=>e-t)))(t,r);return e>=n&&e<=s},C=async(e,n)=>{const s=((e,n)=>{const{containerStart:s,containerSize:o,viewSize:i}=((e,t)=>{const{size:r,start:n}=((e,t=!1)=>{if(t){const{left:t,width:r}=e.getBoundingClientRect();return {size:r,start:t}}const{height:r,top:n}=e.getBoundingClientRect();return {size:r,start:n}})(e,t),s=((e,t)=>["auto","scroll"].includes(getComputedStyle(e).getPropertyValue("overflow-"+(t?"x":"y"))))(e,t);return {containerStart:n,containerSize:s?L(e,t):r,viewSize:s?r:f(t)}})(e,n);return {inner:calcInnerProgress(s,o,i),outer:calcOuterProgress(s,o,i)}})(e,n);e.dispatchEvent(new CustomEvent(y,{detail:{progress:s},bubbles:!0,cancelable:!0,composed:!1}));},S=({target:e,detail:{progress:{inner:t,outer:r}}})=>{var n;w(r,0,1)?(e.style.setProperty(c,t),e.style.setProperty(d,r)):(n=e,[c,d].forEach((e=>n.style.removeProperty(e))));},z=(e,t,r,n,s)=>{w(t,0,1)?e.classList.contains(s)||(e.classList.add(s),e.dispatchEvent(new CustomEvent(r,{bubbles:!0,cancelable:!0,composed:!1}))):e.classList.contains(s)&&(e.classList.remove(s),e.dispatchEvent(new CustomEvent(n,{bubbles:!0,cancelable:!0,composed:!1})));},A=({target:e,detail:{progress:{inner:t}}})=>{z(e,t,u,m,i);},x=({target:e,detail:{progress:{outer:t}}})=>{z(e,t,g,p,l);},_=({target:e})=>{$&&cancelAnimationFrame($),$=requestAnimationFrame((()=>{(async e=>{const t=e.classList.contains(o);Promise.all([e,...e.querySelectorAll(b)].map((e=>C(e,t))));})(e),$=null;}));},B=e=>{[e,...e.querySelectorAll(b)].forEach((e=>{e.addEventListener(y,S),e.addEventListener(y,x),e.addEventListener(y,A);}));};var m$1 = ()=>{(()=>{if(document.getElementById(h))return;const t=document.createElement("style");t.setAttribute("id",h),t.textContent=e,document.head.appendChild(t);})(),(()=>{const e=document.createElement("div");e.setAttribute("id",v),e.classList.add(a),document.body.appendChild(e);})(),Array.from(document.querySelectorAll(E)).forEach((e=>{e.addEventListener("resize",_),e.addEventListener("scroll",_),B(e),_({target:e});})),window.addEventListener("resize",(()=>_({target:document.body}))),window.addEventListener("scroll",(()=>_({target:document.body}))),B(document.body),_({target:document.body}),document.documentElement.classList.add(s);};export{m$1 as default}; \ No newline at end of file diff --git a/dist/scrollerful.mjs b/dist/scrollerful.mjs index 09f7cf0..905f6dc 100644 --- a/dist/scrollerful.mjs +++ b/dist/scrollerful.mjs @@ -1,6 +1,29 @@ /*! scrollerful v0.5.1 | (c) 2022-2023 Rémino Rem | ISC Licence */ var style = ":root{--scrollerful-delay:0s}@media screen{.scrollerful--enabled .scrollerful{min-height:100%}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--snap,.scrollerful--enabled .scrollerful--snap--page,.scrollerful--enabled .scrollerful--snap--page body{scroll-snap-stop:always;scroll-snap-type:y proximity}}.scrollerful--enabled .scrollerful--snap,.scrollerful--enabled .scrollerful--snap--page{overflow-y:auto}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--snap .scrollerful__tray,.scrollerful--enabled .scrollerful--snap--page .scrollerful__tray{scroll-snap-align:start end}}.scrollerful--enabled .scrollerful--snap{height:100%}.scrollerful--enabled .scrollerful--x{display:flex;flex-flow:row nowrap}.scrollerful--enabled .scrollerful--x.scrollerful--snap{overflow-x:auto;overflow-y:hidden}@supports(scroll-snap-stop:always){.scrollerful--enabled .scrollerful--x.scrollerful--snap{scroll-snap-type:x proximity}}.scrollerful--enabled .scrollerful--x .scrollerful__plate{left:0;max-height:none;max-width:100%;top:auto;width:100vw;width:100lvw}.scrollerful--enabled .scrollerful--x .scrollerful__tray{flex-shrink:0;height:auto;width:300vw;width:300lvw}.scrollerful--enabled .scrollerful--x .scrollerful__tray--padding{height:auto;width:100vw;width:100lvw}.scrollerful--enabled .scrollerful__ruler{background:none transparent;border:none;bottom:0;display:block;height:100vh;height:100lvh;left:-200%;pointer-events:none;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100vw;width:100lvw;z-index:-10}.scrollerful--enabled .scrollerful__plate{align-items:center;display:flex;flex-flow:column;height:100vh;height:100lvh;justify-content:center;max-height:100%;overflow:hidden;position:sticky;top:0}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--inner,.scrollerful--enabled .scrollerful__sprite--outer{animation-duration:100s;animation-fill-mode:both;animation-play-state:paused;animation-timing-function:linear}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--inner{animation-delay:calc(var(--scrollerful-progress-inner, 0)*-100s + var(--scrollerful-delay, 0))}.scrollerful--enabled .scrollerful__sprite,.scrollerful--enabled .scrollerful__sprite--outer{animation-delay:calc(var(--scrollerful-progress-outer, 0)*-100s + var(--scrollerful-delay, 0))}.scrollerful--enabled .scrollerful__tray{height:300vh;height:300lvh;position:relative}.scrollerful--enabled .scrollerful__tray--padding{height:100vh;height:100lvh}}"; +const calcInnerProgress = (containerStart, containerSize, viewSize) => { + if (containerSize === viewSize) { + const progress = (((containerStart - viewSize) / (viewSize)) * -1); + + switch (true) { + case containerStart < 0: return progress + case containerStart > 0: return progress - 1 + default: return 0.5 + } + } + + const progress = (((containerStart) / (containerSize - viewSize)) * -1); + + switch (true) { + case containerSize < viewSize: return 1 - progress + default: return progress + } +}; + +const calcOuterProgress = (containerStart, containerSize, viewSize) => ( + ((containerStart - viewSize) / (viewSize + containerSize)) * -1 +); + const SCRIPT_NAME = 'scrollerful'; const CSS_CLASS_ENABLED = `${SCRIPT_NAME}--enabled`; @@ -81,8 +104,8 @@ const sectionProgress = (el, horizontal) => { const { containerStart, containerSize, viewSize } = getContainerCoords(el, horizontal); return { - inner: containerStart / -(containerSize - viewSize), - outer: (containerStart - viewSize) / -(containerSize + viewSize), + inner: calcInnerProgress(containerStart, containerSize, viewSize), + outer: calcOuterProgress(containerStart, containerSize, viewSize), } }; diff --git a/package-lock.json b/package-lock.json index a33932c..d1e4c5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "scrollerful", - "version": "0.5.1", + "version": "0.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "scrollerful", - "version": "0.5.1", + "version": "0.6.0", "license": "ISC", "devDependencies": { "@lopatnov/rollup-plugin-uglify": "^2.1.5", @@ -16,8 +16,10 @@ "eslint": "^8.23.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jasmine": "^4.1.3", "eslint-plugin-no-template-curly-in-string-fix": "^1.0.4", "install": "^0.13.0", + "jasmine": "^4.5.0", "npm": "^9.2.0", "postcss": "^8.4.21", "rollup": "^3.6.0", @@ -1236,6 +1238,16 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/eslint-plugin-jasmine": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jasmine/-/eslint-plugin-jasmine-4.1.3.tgz", + "integrity": "sha512-q8j8KnLH/4uwmPELFZvEyfEcuCuGxXScJaRdqHjOjz064GcfX6aoFbzy5VohZ5QYk2+WvoqMoqDSb9nRLf89GQ==", + "dev": true, + "engines": { + "node": ">=8", + "npm": ">=6" + } + }, "node_modules/eslint-plugin-no-template-curly-in-string-fix": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/eslint-plugin-no-template-curly-in-string-fix/-/eslint-plugin-no-template-curly-in-string-fix-1.0.4.tgz", @@ -1990,6 +2002,25 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/jasmine": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-4.5.0.tgz", + "integrity": "sha512-9olGRvNZyADIwYL9XBNBst5BTU/YaePzuddK+YRslc7rI9MdTIE4r3xaBKbv2GEmzYYUfMOdTR8/i6JfLZaxSQ==", + "dev": true, + "dependencies": { + "glob": "^7.1.6", + "jasmine-core": "^4.5.0" + }, + "bin": { + "jasmine": "bin/jasmine.js" + } + }, + "node_modules/jasmine-core": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.5.0.tgz", + "integrity": "sha512-9PMzyvhtocxb3aXJVOPqBDswdgyAeSB81QnLop4npOpbqnheaTEwPc9ZloQeVswugPManznQBjD8kWDTjlnHuw==", + "dev": true + }, "node_modules/js-sdsl": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", diff --git a/package.json b/package.json index 39845ab..70b8bfa 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,10 @@ "eslint": "^8.23.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jasmine": "^4.1.3", "eslint-plugin-no-template-curly-in-string-fix": "^1.0.4", "install": "^0.13.0", + "jasmine": "^4.5.0", "npm": "^9.2.0", "postcss": "^8.4.21", "rollup": "^3.6.0", @@ -28,6 +30,7 @@ "files": [ "dist/*" ], + "homepage": "https://remino.net/scrollerful/", "keywords": [ "javascript", "css", @@ -45,8 +48,8 @@ "lint": "npx eslint", "lint:fix": "npx eslint --fix", "start": "mansite", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "npx jasmine" }, "type": "module", - "version": "0.5.1" + "version": "0.6.0" } diff --git a/pages/index.html.haml b/pages/index.html.haml deleted file mode 100644 index b25e925..0000000 --- a/pages/index.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -!!! -%html - %head - %meta(http-equiv="Refresh" content="0; url=/scrollerful/") diff --git a/pages/index.html.slim b/pages/index.html.slim new file mode 100644 index 0000000..ebfcb82 --- /dev/null +++ b/pages/index.html.slim @@ -0,0 +1,4 @@ +doctype html +html + head + meta(http-equiv="Refresh" content="0; url=/scrollerful/") diff --git a/pages/layouts/layout.html.haml b/pages/layouts/layout.html.haml deleted file mode 100644 index ea412bb..0000000 --- a/pages/layouts/layout.html.haml +++ /dev/null @@ -1,27 +0,0 @@ -!!! -%html.scrollerful--snap-page(lang="en") - %head - %meta(charset="UTF-8")/ - %title= data.site.title - %meta(content="text/html; charset=UTF-8" http-equiv="Content-Type")/ - %meta(content="IE=edge" http-equiv="X-UA-Compatible")/ - %meta(content="viewport-fit=cover, width=device-width, initial-scale=1.0" name="viewport")/ - %meta{content: data.site.description, name: "description"}/ - %meta{content: data.site.title, property: "og:title"}/ - %meta{content: data.site.description, property: "og:description"}/ - %meta(content="https://remino.net/scrollerful/share-fb.png" property="og:image")/ - %meta(content="630" property="og:image:height")/ - %meta(content="1200" property="og:image:width")/ - %meta{content: data.site.url, property: "og:url"}/ - %meta(content="website" property="og:type")/ - %meta(content="summary_large_image" name="twitter:card")/ - %meta{content: data.site.title, name: "twitter:title"}/ - %meta{content: data.site.description, name: "twitter:description"}/ - %meta(content="https://remino.net/scrollerful/share-tw.png" name="twitter:image")/ - %meta(content="@remino" name="twitter:site")/ - %meta(content="@remino" name="twitter:creator")/ - %meta{content: data.site.url, name: "twitter:url"}/ - = stylesheet_link_tag 'style' - %body(class="#{ current_page.data.body_class || 'scrollerful scrollerful__sprite' }") - = yield - = javascript_include_tag 'script' diff --git a/pages/layouts/layout.html.slim b/pages/layouts/layout.html.slim new file mode 100644 index 0000000..8515249 --- /dev/null +++ b/pages/layouts/layout.html.slim @@ -0,0 +1,28 @@ +doctype html +html.scrollerful--snap-page(lang="en") + head + meta(charset="UTF-8")/ + title= data.site.title + meta(content="text/html; charset=UTF-8" http-equiv="Content-Type")/ + meta(content="IE=edge" http-equiv="X-UA-Compatible")/ + meta(content="viewport-fit=cover, width=device-width, initial-scale=1.0" name="viewport")/ + meta(content=data.site.description name="description")/ + meta(content=data.site.title property="og:title")/ + meta(content=data.site.description property="og:description")/ + meta(content="https://remino.net/scrollerful/share-fb.png" property="og:image")/ + meta(content="630" property="og:image:height")/ + meta(content="1200" property="og:image:width")/ + meta(content=data.site.url property="og:url")/ + meta(content="website" property="og:type")/ + meta(content="summary_large_image" name="twitter:card")/ + meta(content=data.site.title name="twitter:title")/ + meta(content=data.site.description name="twitter:description")/ + meta(content="https://remino.net/scrollerful/share-tw.png" name="twitter:image")/ + meta(content="@remino" name="twitter:site")/ + meta(content="@remino" name="twitter:creator")/ + meta(content=data.site.url name="twitter:url")/ + = stylesheet_link_tag 'style' + = yield_content :head + body(class="#{current_page.data.body_class || 'scrollerful scrollerful__sprite'}") + = yield + = javascript_include_tag 'script' diff --git a/pages/scrollerful/_logo.html.haml b/pages/scrollerful/_logo.html.haml deleted file mode 100644 index 8e210a6..0000000 --- a/pages/scrollerful/_logo.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -%svg.logo(role="img" preserveAspectRatio="xMidYMid slice" role="img" viewBox="-10 -10 1778.08 150.22") - %path.scrollerful__sprite--inner(d="M160.89 96.6c0 24.27-17.38 43.62-80.85 43.62S.16 118.9 0 100.7v-2.46l52.48-3.94.82 2.62c1.8 9.35 11.48 14.1 29.52 14.1 22.8 0 32.31-3.77 32.31-11.97 0-8.2-6.56-13.28-50.68-14.43C20.5 82.33 2.13 65.76 2.13 42.31 2.13 12.14 35.26.33 74.78.33c54.61 0 80.03 16.89 80.03 36.57v1.48l-49.69 3.44-.66-1.97c-1.31-5.25-13.94-9.68-28.37-10-19.19-.49-28.21 2.79-28.21 10 0 8.36 9.02 12.46 61.5 15.25 37.88 2.46 51.5 18.7 51.5 41.49z") - %path.scrollerful__sprite--inner(d="M339.64 92.82c-7.22 29.36-34.77 47.23-83.48 47.23-55.92 0-84.62-25.75-84.62-67.57v-4.92C171.54 25.75 200.08 0 256.17 0c46.41 0 75.6 16.73 82.49 44.44l-43.95 9.18c-4.26-16.4-20.01-23.12-38.54-23.12-18.53 0-38.87 9.51-38.87 34.44v11.15c0 24.27 19.84 33.46 38.87 33.46 19.03 0 36.74-8.2 39.52-25.91z") - %path.scrollerful__sprite--inner(d="M398.19 135.79h-43.62V4.26h102.17c30.18 0 55.76 8.86 55.76 39.69 0 22.47-14.43 32.8-34.11 36.41l34.6 55.43h-48.54l-33.46-53.63h-32.8zm0-103.32v21.48h49.86c11.64 0 18.2-3.28 18.2-10.99 0-7.71-6.56-10.5-18.2-10.5h-49.86z") - %path.scrollerful__sprite--inner(d="M610.9 140.06c-58.38 0-88.07-25.75-88.07-67.57v-5.08c0-41.66 29.52-67.4 88.07-67.4s88.23 25.75 88.23 67.4v5.08c0 41.82-29.68 67.57-88.23 67.57zm0-108.24c-20.83 0-42.31 9.68-42.31 34.6v8.36c0 24.93 21.48 34.6 42.31 34.6 20.83 0 42.48-9.68 42.48-34.6v-8.36c0-24.93-21.48-34.6-42.48-34.6z") - %path.scrollerful__sprite--inner(d="M839.35 105.94v29.85H715.04V4.26h43.62v101.68z") - %path.scrollerful__sprite--inner(d="M978.91 105.94v29.85H854.6V4.26h43.62v101.68z") - %path.scrollerful__sprite--inner(d="M1134.39 135.79H994.17V4.26h136.45v29.85h-92.83V53.3h75.77v30.01h-75.77v22.63h96.6z") - %path.scrollerful__sprite--inner(d="M1194.57 135.79h-43.62V4.26h102.17c30.18 0 55.76 8.86 55.76 39.69 0 22.47-14.43 32.8-34.11 36.41l34.6 55.43h-48.54l-33.46-53.63h-32.8zm0-103.32v21.48h49.86c11.64 0 18.2-3.28 18.2-10.99 0-7.71-6.56-10.5-18.2-10.5h-49.86z") - %path.scrollerful__sprite--inner(d="M1369.4 135.79h-43.62V4.26h135.79v29.85h-92.17v20.83h76.59v30.01h-76.59z") - %path.scrollerful__sprite--inner(d="M1518.64 4.26v78.23c0 19.84 14.43 27.06 30.01 27.06 15.58 0 30.18-7.22 30.18-27.06V4.26h43.62v79.21c0 34.93-24.11 56.74-73.8 56.74s-73.64-21.81-73.64-56.74V4.26h43.62z") - %path.scrollerful__sprite--inner(d="M1768.08 105.94v29.85h-124.31V4.26h43.62v101.68z") diff --git a/pages/scrollerful/_logo.html.slim b/pages/scrollerful/_logo.html.slim new file mode 100644 index 0000000..5464660 --- /dev/null +++ b/pages/scrollerful/_logo.html.slim @@ -0,0 +1,12 @@ +svg.logo(role="img" preserveAspectRatio="xMidYMid slice" viewBox="-10 -10 1778.08 150.22") + path.scrollerful__sprite--inner(d="M160.89 96.6c0 24.27-17.38 43.62-80.85 43.62S.16 118.9 0 100.7v-2.46l52.48-3.94.82 2.62c1.8 9.35 11.48 14.1 29.52 14.1 22.8 0 32.31-3.77 32.31-11.97 0-8.2-6.56-13.28-50.68-14.43C20.5 82.33 2.13 65.76 2.13 42.31 2.13 12.14 35.26.33 74.78.33c54.61 0 80.03 16.89 80.03 36.57v1.48l-49.69 3.44-.66-1.97c-1.31-5.25-13.94-9.68-28.37-10-19.19-.49-28.21 2.79-28.21 10 0 8.36 9.02 12.46 61.5 15.25 37.88 2.46 51.5 18.7 51.5 41.49z") + path.scrollerful__sprite--inner(d="M339.64 92.82c-7.22 29.36-34.77 47.23-83.48 47.23-55.92 0-84.62-25.75-84.62-67.57v-4.92C171.54 25.75 200.08 0 256.17 0c46.41 0 75.6 16.73 82.49 44.44l-43.95 9.18c-4.26-16.4-20.01-23.12-38.54-23.12-18.53 0-38.87 9.51-38.87 34.44v11.15c0 24.27 19.84 33.46 38.87 33.46 19.03 0 36.74-8.2 39.52-25.91z") + path.scrollerful__sprite--inner(d="M398.19 135.79h-43.62V4.26h102.17c30.18 0 55.76 8.86 55.76 39.69 0 22.47-14.43 32.8-34.11 36.41l34.6 55.43h-48.54l-33.46-53.63h-32.8zm0-103.32v21.48h49.86c11.64 0 18.2-3.28 18.2-10.99 0-7.71-6.56-10.5-18.2-10.5h-49.86z") + path.scrollerful__sprite--inner(d="M610.9 140.06c-58.38 0-88.07-25.75-88.07-67.57v-5.08c0-41.66 29.52-67.4 88.07-67.4s88.23 25.75 88.23 67.4v5.08c0 41.82-29.68 67.57-88.23 67.57zm0-108.24c-20.83 0-42.31 9.68-42.31 34.6v8.36c0 24.93 21.48 34.6 42.31 34.6 20.83 0 42.48-9.68 42.48-34.6v-8.36c0-24.93-21.48-34.6-42.48-34.6z") + path.scrollerful__sprite--inner(d="M839.35 105.94v29.85H715.04V4.26h43.62v101.68z") + path.scrollerful__sprite--inner(d="M978.91 105.94v29.85H854.6V4.26h43.62v101.68z") + path.scrollerful__sprite--inner(d="M1134.39 135.79H994.17V4.26h136.45v29.85h-92.83V53.3h75.77v30.01h-75.77v22.63h96.6z") + path.scrollerful__sprite--inner(d="M1194.57 135.79h-43.62V4.26h102.17c30.18 0 55.76 8.86 55.76 39.69 0 22.47-14.43 32.8-34.11 36.41l34.6 55.43h-48.54l-33.46-53.63h-32.8zm0-103.32v21.48h49.86c11.64 0 18.2-3.28 18.2-10.99 0-7.71-6.56-10.5-18.2-10.5h-49.86z") + path.scrollerful__sprite--inner(d="M1369.4 135.79h-43.62V4.26h135.79v29.85h-92.17v20.83h76.59v30.01h-76.59z") + path.scrollerful__sprite--inner(d="M1518.64 4.26v78.23c0 19.84 14.43 27.06 30.01 27.06 15.58 0 30.18-7.22 30.18-27.06V4.26h43.62v79.21c0 34.93-24.11 56.74-73.8 56.74s-73.64-21.81-73.64-56.74V4.26h43.62z") + path.scrollerful__sprite--inner(d="M1768.08 105.94v29.85h-124.31V4.26h43.62v101.68z") diff --git a/pages/scrollerful/demo/demo.html.slim b/pages/scrollerful/demo/demo.html.slim new file mode 100644 index 0000000..76c2b4d --- /dev/null +++ b/pages/scrollerful/demo/demo.html.slim @@ -0,0 +1,20 @@ +- classes = %W(scrollerful scrollerful__sprite demo--#{direction} demo--#{size}) +- classes.push 'scrollerful--x' if direction == 'horizontal' +- current_page.data.body_class = classes.join ' ' + +- content_for :head + = stylesheet_link_tag 'demo' + +- 5.times do |i| + .scrollerful__tray + .scrollerful__plate + .scrollerful__sprite + h1.scrollerful__sprite + output + '% + .scrollerful__tray + .scrollerful__plate + .scrollerful__sprite + .piechart.scrollerful__sprite + += javascript_include_tag 'demo' diff --git a/pages/scrollerful/horizontal.html.haml b/pages/scrollerful/horizontal.html.slim similarity index 67% rename from pages/scrollerful/horizontal.html.haml rename to pages/scrollerful/horizontal.html.slim index c4eaaec..afc4576 100644 --- a/pages/scrollerful/horizontal.html.haml +++ b/pages/scrollerful/horizontal.html.slim @@ -3,6 +3,6 @@ body_class: scrollerful scrollerful--x scrollerful--snap --- - (0..9).each do |i| - %section.scrollerful__tray + section.scrollerful__tray .scrollerful__plate.scrollerful__sprite--inner - %h1.scrollerful__sprite--inner= i + h1.scrollerful__sprite--inner= i diff --git a/pages/scrollerful/index.html.haml b/pages/scrollerful/index.html.haml deleted file mode 100644 index 483f528..0000000 --- a/pages/scrollerful/index.html.haml +++ /dev/null @@ -1,22 +0,0 @@ -%header.scrollerful__tray - .scrollerful__plate - .scroll-down.scrollerful__sprite - - (1..4).each do |i| - .scroll-down__arrow - %h1.scrollerful__sprite - = partial 'logo' -%section.scrollerful__tray - .scrollerful__plate - %h2 - %span.scrollerful__sprite--inner Scroll - %span.scrollerful__sprite--inner with - %span.scrollerful__sprite--inner - %strong.scrollerful__sprite style! - %p - %strong.scrollerful__sprite--inner Yeah! -%nav.scrollerful__tray - .scrollerful__plate - .scrollerful__sprite - %a(href="https://github.com/remino/scrollerful") - %span.caption Get it now! - %span.bg.scrollerful__sprite diff --git a/pages/scrollerful/index.html.slim b/pages/scrollerful/index.html.slim new file mode 100644 index 0000000..437d6be --- /dev/null +++ b/pages/scrollerful/index.html.slim @@ -0,0 +1,22 @@ +header.scrollerful__tray + .scrollerful__plate + .scroll-down.scrollerful__sprite + - (1..4).each do |i| + .scroll-down__arrow + h1.scrollerful__sprite + = partial 'logo' +section.scrollerful__tray + .scrollerful__plate + h2 + span.scrollerful__sprite--inner Scroll + span.scrollerful__sprite--inner with + span.scrollerful__sprite--inner + strong.scrollerful__sprite style! + p + strong.scrollerful__sprite--inner Yeah! +nav.scrollerful__tray + .scrollerful__plate + .scrollerful__sprite + a(href="https://github.com/remino/scrollerful") + span.caption Get it now! + span.bg.scrollerful__sprite diff --git a/rollup.config.js b/rollup.config.js index d75fde2..87f0a93 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -96,4 +96,14 @@ export default [ }, plugins: pluginsCompact, }, + { + ...options, + input: 'assets/js/demo.js', + output: { + file: '.build/js/scrollerful/demo.js', + format: 'umd', + name: 'scrollerful', + }, + plugins: pluginsCompact, + }, ] diff --git a/spec/src/calc.spec.js b/spec/src/calc.spec.js new file mode 100644 index 0000000..91715d8 --- /dev/null +++ b/spec/src/calc.spec.js @@ -0,0 +1,73 @@ +import { calcInnerProgress, calcOuterProgress } from '../../src/calc.js' + +describe('src/calc.js', () => { + describe('calcInnerProgress()', () => { + const cases = [ + // testNumber, containerStart, containerSize, viewSize, expected + + // Container taller than viewport + ['1.01', 100, 200, 100, -1], + ['1.02', 0, 200, 100, 0], + ['1.03', -50, 200, 100, 0.5], + ['1.04', -100, 200, 100, 1], + ['1.05', -200, 200, 100, 2], + + // Container shorter than viewport + ['1.06', 100, 50, 100, -1], + ['1.07', 50, 50, 100, 0], + ['1.08', 25, 50, 100, 0.5], + ['1.09', 0, 50, 100, 1], + ['1.10', -50, 50, 100, 2], + + // Container of same height than viewport + ['1.11', 100, 100, 100, -1], + ['1.12', 50, 100, 100, -0.5], + ['1.13', 0, 100, 100, 0.5], + ['1.14', -50, 100, 100, 1.5], + ['1.15', -100, 100, 100, 2], + ] + + cases.forEach(([ + testNumber, containerStart, containerSize, viewSize, expected, + ]) => { + it(`(${testNumber}) returns inner progress of ${expected} for containerStart ${containerStart}, containerSize ${containerSize}, and viewSize ${viewSize}`, () => { + expect(calcInnerProgress(containerStart, containerSize, viewSize)).toBe(expected) + }) + }) + }) + + describe('calcOuterProgress()', () => { + const cases = [ + // testNumber, containerStart, containerSize, viewSize, expected + + // Container taller than viewport + ['2.01', 100, 200, 100, 0], + ['2.02', 0, 200, 100, 1 / 3], + ['2.03', -50, 200, 100, 0.5], + ['2.04', -100, 200, 100, 2 / 3], + ['2.05', -200, 200, 100, 1], + + // Container shorter than viewport + ['2.06', 100, 50, 100, 0], + ['2.07', 50, 50, 100, 1 / 3], + ['2.08', 25, 50, 100, 0.5], + ['2.09', 0, 50, 100, 2 / 3], + ['2.10', -50, 50, 100, 1], + + // Container of same height than viewport + ['2.11', 100, 100, 100, 0], + ['2.12', 50, 100, 100, 0.25], + ['2.13', 0, 100, 100, 0.5], + ['2.14', -50, 100, 100, 0.75], + ['2.15', -100, 100, 100, 1], + ] + + cases.forEach(([ + testNumber, containerStart, containerSize, viewSize, expected, + ]) => { + it(`(${testNumber}) returns outer progress of ${expected} for containerStart ${containerStart}, containerSize ${containerSize}, and viewSize ${viewSize}`, () => { + expect(calcOuterProgress(containerStart, containerSize, viewSize)).toBe(expected) + }) + }) + }) +}) diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json new file mode 100644 index 0000000..c69c891 --- /dev/null +++ b/spec/support/jasmine.json @@ -0,0 +1,13 @@ +{ + "spec_dir": "spec", + "spec_files": [ + "**/*[sS]pec.?(m)js" + ], + "helpers": [ + "helpers/**/*.?(m)js" + ], + "env": { + "stopSpecOnExpectationFailure": false, + "random": true + } +} \ No newline at end of file diff --git a/src/calc.js b/src/calc.js new file mode 100644 index 0000000..7de8ba2 --- /dev/null +++ b/src/calc.js @@ -0,0 +1,24 @@ +export const calcContainerEnd = (containerStart, containerSize) => containerStart + containerSize + +export const calcInnerProgress = (containerStart, containerSize, viewSize) => { + if (containerSize === viewSize) { + const progress = (((containerStart - viewSize) / (viewSize)) * -1) + + switch (true) { + case containerStart < 0: return progress + case containerStart > 0: return progress - 1 + default: return 0.5 + } + } + + const progress = (((containerStart) / (containerSize - viewSize)) * -1) + + switch (true) { + case containerSize < viewSize: return 1 - progress + default: return progress + } +} + +export const calcOuterProgress = (containerStart, containerSize, viewSize) => ( + ((containerStart - viewSize) / (viewSize + containerSize)) * -1 +) diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..a5ef084 --- /dev/null +++ b/src/main.js @@ -0,0 +1,206 @@ +import style from './scrollerful.sass' +import { calcInnerProgress, calcOuterProgress } from './calc.js' + +const SCRIPT_NAME = 'scrollerful' + +const CSS_CLASS_ENABLED = `${SCRIPT_NAME}--enabled` +const CSS_CLASS_HORIZONTAL = `${SCRIPT_NAME}--x` +const CSS_CLASS_INSIDE_INNER = `${SCRIPT_NAME}--inside--inner` +const CSS_CLASS_INSIDE_OUTER = `${SCRIPT_NAME}--inside--outer` +const CSS_CLASS_RULER = `${SCRIPT_NAME}__ruler` +const CSS_PROP_PROGRESS_INNER = `--${SCRIPT_NAME}-progress-inner` +const CSS_PROP_PROGRESS_OUTER = `--${SCRIPT_NAME}-progress-outer` +const EVENT_INNER_ENTER = `${SCRIPT_NAME}innerenter` +const EVENT_INNER_EXIT = `${SCRIPT_NAME}innerexit` +const EVENT_OUTER_ENTER = `${SCRIPT_NAME}outerenter` +const EVENT_OUTER_EXIT = `${SCRIPT_NAME}outerexit` +const EVENT_SCROLL = `${SCRIPT_NAME}scroll` +const SEL_SCROLL = `.${SCRIPT_NAME}` +const SEL_TRAY = `.${SCRIPT_NAME}__tray` +// TODO Predict internia to smoothen animations +const SMOOTHING_FACTOR = 50 +const EL_ID_RULER = `${SCRIPT_NAME}_ruler` +const EL_ID_STYLE = `${SCRIPT_NAME}_style` + +let requestId + +const getElScrollSize = (el, horizontal = false) => (horizontal ? el.scrollWidth : el.scrollHeight) +const getStyleEl = () => document.getElementById(EL_ID_STYLE) +const getViewportRect = () => document.getElementById(EL_ID_RULER).getBoundingClientRect() +const getViewportSize = horizontal => getViewportRect()[horizontal ? 'width' : 'height'] +const showsOverflow = (el, horizontal) => ['auto', 'scroll'] + .includes(getComputedStyle(el).getPropertyValue(`overflow-${horizontal ? 'x' : 'y'}`)) +const sortNums = (...nums) => nums.sort((a, b) => a - b) + +const isWithin = (num, a, b) => { + const [min, max] = sortNums(a, b) + return num >= min && num <= max +} + +const addEnabledClass = () => { + document.documentElement.classList.add(CSS_CLASS_ENABLED) +} + +const addRuler = () => { + const ruler = document.createElement('div') + ruler.setAttribute('id', EL_ID_RULER) + ruler.classList.add(CSS_CLASS_RULER) + document.body.appendChild(ruler) +} + +const addStyle = () => { + if (getStyleEl()) return + + const styleEl = document.createElement('style') + styleEl.setAttribute('id', EL_ID_STYLE) + styleEl.textContent = style + + document.head.appendChild(styleEl) +} + +const getElAxisCoords = (el, horizontal = false) => { + if (horizontal) { + const { left, width } = el.getBoundingClientRect() + return { size: width, start: left } + } + + const { height, top } = el.getBoundingClientRect() + return { size: height, start: top } +} + +const getContainerCoords = (el, horizontal) => { + const { size, start } = getElAxisCoords(el, horizontal) + const overflow = showsOverflow(el, horizontal) + + return { + containerStart: start, + containerSize: overflow ? getElScrollSize(el, horizontal) : size, + viewSize: overflow ? size : getViewportSize(horizontal), + } +} + +const sectionProgress = (el, horizontal) => { + const { containerStart, containerSize, viewSize } = getContainerCoords(el, horizontal) + + return { + inner: calcInnerProgress(containerStart, containerSize, viewSize), + outer: calcOuterProgress(containerStart, containerSize, viewSize), + } +} + +const processSection = async (el, horizontal) => { + const progress = sectionProgress(el, horizontal) + + el.dispatchEvent( + new CustomEvent(EVENT_SCROLL, { + detail: { progress }, + bubbles: true, + cancelable: true, + composed: false, + }), + ) +} + +const removeStyleProperties = (el, ...names) => { + names.forEach(name => el.style.removeProperty(name)) +} + +const setStyleVars = ({ + target, + detail: { + progress: { inner, outer }, + }, +}) => { + if (!isWithin(outer, 0, 1)) { + removeStyleProperties( + target, + CSS_PROP_PROGRESS_INNER, + CSS_PROP_PROGRESS_OUTER, + ) + return + } + + target.style.setProperty(CSS_PROP_PROGRESS_INNER, inner) + target.style.setProperty(CSS_PROP_PROGRESS_OUTER, outer) +} + +const triggerEnterExit = (target, progress, eventEnter, eventExit, className) => { + if (!isWithin(progress, 0, 1)) { + if (target.classList.contains(className)) { + target.classList.remove(className) + + target.dispatchEvent( + new CustomEvent(eventExit, { + bubbles: true, + cancelable: true, + composed: false, + }), + ) + } + } else if (!target.classList.contains(className)) { + target.classList.add(className) + + target.dispatchEvent( + new CustomEvent(eventEnter, { + bubbles: true, + cancelable: true, + composed: false, + }), + ) + } +} + +const triggerInnerEnterExit = ({ target, detail: { progress: { inner } } }) => { + triggerEnterExit(target, inner, EVENT_INNER_ENTER, EVENT_INNER_EXIT, CSS_CLASS_INSIDE_INNER) +} + +const triggerOuterEnterExit = ({ target, detail: { progress: { outer } } }) => { + triggerEnterExit(target, outer, EVENT_OUTER_ENTER, EVENT_OUTER_EXIT, CSS_CLASS_INSIDE_OUTER) +} + +const scrollFrame = async target => { + const horizontal = target.classList.contains(CSS_CLASS_HORIZONTAL) + + Promise.all([ + target, + ...target.querySelectorAll(SEL_TRAY), + ].map(el => processSection(el, horizontal))) +} + +const scroll = ({ target }) => { + if (requestId) cancelAnimationFrame(requestId) + + requestId = requestAnimationFrame(() => { + scrollFrame(target) + requestId = null + }) +} + +const addScrollListeners = scrollEl => { + [scrollEl, ...scrollEl.querySelectorAll(SEL_TRAY)].forEach(el => { + el.addEventListener(EVENT_SCROLL, setStyleVars) + el.addEventListener(EVENT_SCROLL, triggerOuterEnterExit) + el.addEventListener(EVENT_SCROLL, triggerInnerEnterExit) + }) +} + +const scrollerful = () => { + addStyle() + addRuler() + + Array.from(document.querySelectorAll(SEL_SCROLL)).forEach(target => { + target.addEventListener('resize', scroll) + target.addEventListener('scroll', scroll) + addScrollListeners(target) + scroll({ target }) + }) + + window.addEventListener('resize', () => scroll({ target: document.body })) + window.addEventListener('scroll', () => scroll({ target: document.body })) + addScrollListeners(document.body) + scroll({ target: document.body }) + + addEnabledClass() +} + +export default scrollerful diff --git a/src/scrollerful.js b/src/scrollerful.js index a24a919..da1f2ad 100644 --- a/src/scrollerful.js +++ b/src/scrollerful.js @@ -1,203 +1,3 @@ -import style from './scrollerful.sass' - -const SCRIPT_NAME = 'scrollerful' - -const CSS_CLASS_ENABLED = `${SCRIPT_NAME}--enabled` -const CSS_CLASS_HORIZONTAL = `${SCRIPT_NAME}--x` -const CSS_CLASS_INSIDE_INNER = `${SCRIPT_NAME}--inside--inner` -const CSS_CLASS_INSIDE_OUTER = `${SCRIPT_NAME}--inside--outer` -const CSS_CLASS_RULER = `${SCRIPT_NAME}__ruler` -const CSS_PROP_PROGRESS_INNER = `--${SCRIPT_NAME}-progress-inner` -const CSS_PROP_PROGRESS_OUTER = `--${SCRIPT_NAME}-progress-outer` -const EVENT_INNER_ENTER = `${SCRIPT_NAME}innerenter` -const EVENT_INNER_EXIT = `${SCRIPT_NAME}innerexit` -const EVENT_OUTER_ENTER = `${SCRIPT_NAME}outerenter` -const EVENT_OUTER_EXIT = `${SCRIPT_NAME}outerexit` -const EVENT_SCROLL = `${SCRIPT_NAME}scroll` -const SEL_SCROLL = `.${SCRIPT_NAME}` -const SEL_TRAY = `.${SCRIPT_NAME}__tray` -const EL_ID_RULER = `${SCRIPT_NAME}_ruler` -const EL_ID_STYLE = `${SCRIPT_NAME}_style` - -let requestId - -const getElScrollSize = (el, horizontal = false) => (horizontal ? el.scrollWidth : el.scrollHeight) -const getStyleEl = () => document.getElementById(EL_ID_STYLE) -const getViewportRect = () => document.getElementById(EL_ID_RULER).getBoundingClientRect() -const getViewportSize = horizontal => getViewportRect()[horizontal ? 'width' : 'height'] -const showsOverflow = (el, horizontal) => ['auto', 'scroll'] - .includes(getComputedStyle(el).getPropertyValue(`overflow-${horizontal ? 'x' : 'y'}`)) -const sortNums = (...nums) => nums.sort((a, b) => a - b) - -const isWithin = (num, a, b) => { - const [min, max] = sortNums(a, b) - return num >= min && num <= max -} - -const addEnabledClass = () => { - document.documentElement.classList.add(CSS_CLASS_ENABLED) -} - -const addRuler = () => { - const ruler = document.createElement('div') - ruler.setAttribute('id', EL_ID_RULER) - ruler.classList.add(CSS_CLASS_RULER) - document.body.appendChild(ruler) -} - -const addStyle = () => { - if (getStyleEl()) return - - const styleEl = document.createElement('style') - styleEl.setAttribute('id', EL_ID_STYLE) - styleEl.textContent = style - - document.head.appendChild(styleEl) -} - -const getElAxisCoords = (el, horizontal = false) => { - if (horizontal) { - const { left, width } = el.getBoundingClientRect() - return { size: width, start: left } - } - - const { height, top } = el.getBoundingClientRect() - return { size: height, start: top } -} - -const getContainerCoords = (el, horizontal) => { - const { size, start } = getElAxisCoords(el, horizontal) - const overflow = showsOverflow(el, horizontal) - - return { - containerStart: start, - containerSize: overflow ? getElScrollSize(el, horizontal) : size, - viewSize: overflow ? size : getViewportSize(horizontal), - } -} - -const sectionProgress = (el, horizontal) => { - const { containerStart, containerSize, viewSize } = getContainerCoords(el, horizontal) - - return { - inner: containerStart / -(containerSize - viewSize), - outer: (containerStart - viewSize) / -(containerSize + viewSize), - } -} - -const processSection = async (el, horizontal) => { - const progress = sectionProgress(el, horizontal) - - el.dispatchEvent( - new CustomEvent(EVENT_SCROLL, { - detail: { progress }, - bubbles: true, - cancelable: true, - composed: false, - }), - ) -} - -const removeStyleProperties = (el, ...names) => { - names.forEach(name => el.style.removeProperty(name)) -} - -const setStyleVars = ({ - target, - detail: { - progress: { inner, outer }, - }, -}) => { - if (!isWithin(outer, 0, 1)) { - removeStyleProperties( - target, - CSS_PROP_PROGRESS_INNER, - CSS_PROP_PROGRESS_OUTER, - ) - return - } - - target.style.setProperty(CSS_PROP_PROGRESS_INNER, inner) - target.style.setProperty(CSS_PROP_PROGRESS_OUTER, outer) -} - -const triggerEnterExit = (target, progress, eventEnter, eventExit, className) => { - if (!isWithin(progress, 0, 1)) { - if (target.classList.contains(className)) { - target.classList.remove(className) - - target.dispatchEvent( - new CustomEvent(eventExit, { - bubbles: true, - cancelable: true, - composed: false, - }), - ) - } - } else if (!target.classList.contains(className)) { - target.classList.add(className) - - target.dispatchEvent( - new CustomEvent(eventEnter, { - bubbles: true, - cancelable: true, - composed: false, - }), - ) - } -} - -const triggerInnerEnterExit = ({ target, detail: { progress: { inner } } }) => { - triggerEnterExit(target, inner, EVENT_INNER_ENTER, EVENT_INNER_EXIT, CSS_CLASS_INSIDE_INNER) -} - -const triggerOuterEnterExit = ({ target, detail: { progress: { outer } } }) => { - triggerEnterExit(target, outer, EVENT_OUTER_ENTER, EVENT_OUTER_EXIT, CSS_CLASS_INSIDE_OUTER) -} - -const scrollFrame = async target => { - const horizontal = target.classList.contains(CSS_CLASS_HORIZONTAL) - - Promise.all([ - target, - ...target.querySelectorAll(SEL_TRAY), - ].map(el => processSection(el, horizontal))) -} - -const scroll = ({ target }) => { - if (requestId) cancelAnimationFrame(requestId) - - requestId = requestAnimationFrame(() => { - scrollFrame(target) - requestId = null - }) -} - -const addScrollListeners = scrollEl => { - [scrollEl, ...scrollEl.querySelectorAll(SEL_TRAY)].forEach(el => { - el.addEventListener(EVENT_SCROLL, setStyleVars) - el.addEventListener(EVENT_SCROLL, triggerOuterEnterExit) - el.addEventListener(EVENT_SCROLL, triggerInnerEnterExit) - }) -} - -const scrollerful = () => { - addStyle() - addRuler() - - Array.from(document.querySelectorAll(SEL_SCROLL)).forEach(target => { - target.addEventListener('resize', scroll) - target.addEventListener('scroll', scroll) - addScrollListeners(target) - scroll({ target }) - }) - - window.addEventListener('resize', () => scroll({ target: document.body })) - window.addEventListener('scroll', () => scroll({ target: document.body })) - addScrollListeners(document.body) - scroll({ target: document.body }) - - addEnabledClass() -} +import scrollerful from './main.js' export default scrollerful