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