animbase
Version:
Declarative animation engine
2 lines (1 loc) • 9.39 kB
JavaScript
var AnimBase=function(){"use strict";const t=t=>t.match(/^([\w]{2})([\w]{2})([\w]{2})([\w]{2})$/).slice(1).map((t=>parseInt(t,16))),e=t=>{const e=t.match(/^#(?:([0-f]{3})|([0-f]{4})|([0-f]{6})|([0-f]{8}))$/);if(!e)throw Error("Unsupported color format");const[,n,o,a,r]=e;return n?n.split("").map((t=>t+t)).join("")+"ff":o?o.split("").map((t=>t+t)).join(""):a?a+"ff":r},n={linear:t=>t,ease:t=>t*t*(3-2*t),in:t=>t*t*t,out:t=>1-Math.pow(1-t,3),inOut:t=>t<.5?4*t*t*t:1-Math.pow(-2*t+2,3)/2,inQuad:t=>t*t,outQuad:t=>t*(2-t),inOutQuad:t=>t<.5?2*t*t:(4-2*t)*t-1,inCubic:t=>t*t*t,outCubic:t=>--t*t*t+1,inOutCubic:t=>t<.5?4*t*t*t:(t-1)*(2*t-2)*(2*t-2)+1,inQuart:t=>t*t*t*t,outQuart:t=>1- --t*t*t*t,inOutQuart:t=>t<.5?8*t*t*t*t:1-8*--t*t*t*t,inQuint:t=>t*t*t*t*t,outQuint:t=>1+--t*t*t*t*t,inOutQuint:t=>t<.5?16*t*t*t*t*t:1+16*--t*t*t*t*t,inSine:t=>1-Math.cos(t*Math.PI/2),outSine:t=>Math.sin(t*Math.PI/2),inOutSine:t=>-(Math.cos(Math.PI*t)-1)/2,inExpo:t=>0===t?0:Math.pow(2,10*(t-1)),outExpo:t=>1===t?1:1-Math.pow(2,-10*t),inOutExpo:t=>0===t?0:1===t?1:t<.5?Math.pow(2,20*t-10)/2:(2-Math.pow(2,10-20*t))/2,inCirc:t=>1-Math.sqrt(1-Math.pow(t,2)),outCirc:t=>Math.sqrt(1-Math.pow(t-1,2)),inOutCirc:t=>t<.5?(1-Math.sqrt(1-Math.pow(2*t,2)))/2:(Math.sqrt(1-Math.pow(-2*t+2,2))+1)/2,inBack:t=>2.70158*t*t*t-1.70158*t*t,outBack:t=>1+2.70158*--t*t*t+1.70158*t*t,inOutBack:t=>t<.5?Math.pow(2*t,2)*(7.189819*t-2.5949095)/2:(Math.pow(2*t-2,2)*(3.5949095*(2*t-2)+2.5949095)+2)/2,inElastic:t=>0===t?0:1===t?1:-Math.pow(2,10*t-10)*Math.sin(2*(10*t-10.75)*Math.PI/3),outElastic:t=>0===t?0:1===t?1:Math.pow(2,-10*t)*Math.sin(2*(10*t-.75)*Math.PI/3)+1,inOutElastic:t=>0===t?0:1===t?1:t<.5?-Math.pow(2,20*t-10)*Math.sin(2*(20*t-11.125)*Math.PI/4.5)/2:Math.pow(2,-20*t+10)*Math.sin(2*(20*t-11.125)*Math.PI/4.5)/2+1,inBounce:t=>1-n.outBounce(1-t),outBounce:t=>t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375,inOutBounce:t=>t<.5?(1-n.outBounce(1-2*t))/2:(1+n.outBounce(2*t-1))/2,spring:(t,e=4.5,n=6)=>1-Math.cos(t*e*Math.PI)*Math.exp(-t*n)},o=(t,e,n,o,a)=>o+(t-e)*(a-o)/(n-e);let a={color:/#[0-f]{3,}/,number:/-?\d+(?:\.\d+)?/,unit:/[%a-zA-Z]*/,func:/[a-zA-Z]+/};a={...a,numberWithOptionalUnit:new RegExp(`(?:(${a.color.source})|(?:(${a.number.source})(${a.unit.source})?))`)},a={...a,numberWithOptionalUnitAndFunc:new RegExp(`${a.numberWithOptionalUnit.source}(?:\\.(${a.func.source}))?`)};const r=t=>{const e=RegExp(a.numberWithOptionalUnitAndFunc.source,"g");let o,r=[];for(;null!==(o=e.exec(t));){let[t,a,i,s,l]=o;if(l&&!n[l])throw Error(`Invalid easing function: ${l}`);r.push({type:a?"color":"number",text:t,value:a||parseFloat(i),unit:s,func:l,lastIndex:e.lastIndex})}return r};function i(t,e,n){let o=[...e].reverse(),a=[...n].reverse();for(let e=0;e<o.length;e++){const n=o[e].lastIndex-o[e].text.length,r=o[e].lastIndex;t=t.substring(0,n)+a[e]+t.substring(r)}return t}const s=({startValue:a,endValue:r,startFrame:i=0,endFrame:s,currentFrame:l,func:c="linear",type:u}={})=>{if(a===r)return a;let m=o(l,i,s,0,1);const f=n[c](m);return"color"===u?((n,o,a)=>{const r=t(e(o)),i=t(e(a));return"#"+r.map(((t,e)=>Math.round(Math.max(0,Math.min(255,t+(i[e]-t)*n))).toString(16).padStart(2,"0"))).join("")})(f,a,r):o(f,0,1,a,r)};class l{constructor(t,{init:e=null,config:n=null}={}){this.element=t,this.setConfig({init:e,config:n})}setConfig({init:t=null,config:e=null}={}){const n=this.element.dataset.animInit,o=this.element.dataset.animConfig;if(!t||!e){try{t=n?JSON.parse(n):{}}catch(t){console.debug(n),console.error(t)}try{e=o?JSON.parse(o):{}}catch(t){console.debug(o),console.error(t)}}this.rawInit=t,this.rawConfig=e;const a={};let i=0,s={},l={};for(let[e,n]of Object.entries(t))l[e]=r(n),s[e]=[...l[e]];for(let[t,n]of Object.entries(e)){if("0"===t)continue;const e=parseInt(t);a[t]={startFrame:i,endFrame:e,ref:s,props:{}};for(let[e,o]of Object.entries(n)){const n=r(o);s={...s,[e]:n},a[t].props[e]=n}i=e+1}this.initial=t,this.initialRef=l,this.styleConfig=a}update(t,e=!1){let n={};const o=Object.entries(this.styleConfig),a=o.find((([,e])=>t>=e.startFrame&&t<=e.endFrame));if(a){const o=a[1];for(let[a,r]of Object.entries(o.ref)){const l=o.props[a]?o.props[a].map(((e,n)=>`${s({startValue:r[n].value,endValue:e.value,startFrame:o.startFrame,endFrame:o.endFrame,currentFrame:t,func:e.func,type:e.type})}${e.unit||""}`)):r.map((t=>`${t.value}${t.unit||""}`)),c=i(this.initial[a],this.initialRef[a],l);e?n[a]=c:this.element.style[a]=c}}else{const t=o.at(-1)[1];for(let[o,a]of Object.entries(t.ref)){const r=t.props[o]?t.props[o].map((t=>`${t.value}${t.unit||""}`)):a.map((t=>`${t.value}${t.unit||""}`)),s=i(this.initial[o],this.initialRef[o],r);e?n[o]=s:this.element.style[o]=s}}return e?n:void 0}}class c{constructor(t,e={}){this.getTimelineValue=t,this.animatedElements=new Map,this.timelineValue=0,this.elementSelector=e.elementSelector||"[data-anim-config]",this.autoCollect=e.autoCollect??!0,this.autoUpdate=e.autoUpdate??!0,this.autoCollect&&this.collectAnimatedElements(),this.autoUpdate&&this.updateTimeline()}collectAnimatedElements(){document.querySelectorAll(this.elementSelector).forEach((t=>this.addElement(t)))}addElement(t,e={}){const n=t instanceof l?t:new l(t,e);this.animatedElements.set(n.element,n)}removeElement(t){this.animatedElements.delete(t)}updateTimeline(){this.timelineValue=this.getTimelineValue(),this.updateAllAnimatedElements()}updateAllAnimatedElements(){this.animatedElements.forEach((t=>t.update(this.timelineValue)))}}var u=Object.freeze({__proto__:null,AnimatedElement:l,TimelineAnimator:c,easingFunctions:n,linearInterpolation:o,parseExpression:r});const m=new Map;let f={};function p(t,e={}){let{configSelector:n,elementSelector:o,...a}=e;if(n){const t=document.querySelector(n);t?.dataset?.animTriggerConfig&&(a=JSON.parse(t.dataset.animTriggerConfig))}const r=a.max||100,i=1e3/(parseFloat(a.speed)||60)*r,s=a.autostart||!1;let l;return l=new c((()=>l?.state?.frame??(a.reverse?r:0)),o?{elementSelector:o}:{autoCollect:!1,autoUpdate:!1}),l.state={frame:0,max:r,duration:i,running:!1,startTime:0,pauseTime:0,once:a.once||!1,reverse:a.reverse||!1,onStart:a.onStart||null,onFinish:a.onFinish||null,from:null,to:null},m.set(t,l),s&&f.trigger(t),l}function h(t){const e=t.state,n=()=>{let o=(performance.now()-e.startTime)/e.duration;o=Math.max(0,Math.min(1,o));const a=(e.to??e.max)-(e.from??0);if(e.frame=Math.floor((e.from??0)+a*o),t.updateTimeline(),o>=1){if(e.once)return e.running=!1,void(e.onFinish&&e.onFinish());e.startTime=performance.now(),e.onFinish&&e.onFinish()}e.running&&requestAnimationFrame(n)};n()}["trigger","pause","resume","stop","seek","setOnce","setReverse","setHooks"].forEach((t=>{f[t]=function(e="default",n){const o=m.get(e);if(!o||!o.state)return;const a=o.state;switch(t){case"trigger":const t=n||{};if(!a.running){a.running=!0,"function"==typeof t.onStart&&(a.onStart=t.onStart),"function"==typeof t.onFinish&&(a.onFinish=t.onFinish),a.reverse=t.reverse??a.reverse,a.once=t.once??a.once,a.max=t.max??a.max,a.from="number"==typeof t.from?t.from:a.reverse?a.max:0,a.to="number"==typeof t.to?t.to:a.reverse?0:a.max,a.frame=a.from;const e=t.speed||(a.duration?1e3*Math.abs(a.to-a.from)/a.duration:60);a.duration=1e3/e*Math.abs(a.to-a.from),a.startTime=performance.now(),h(o)}break;case"pause":a.running=!1,a.pauseTime=performance.now();break;case"resume":const e=performance.now()-a.pauseTime;a.startTime+=e,a.running=!0,h(o);break;case"stop":a.running=!1,a.frame=a.reverse?a.max:0,o.updateTimeline();break;case"seek":a.frame=Math.max(0,Math.min(a.max,n||0)),o.updateTimeline();break;case"setOnce":a.once=!!n;break;case"setReverse":a.reverse=!!n;break;case"setHooks":const{onStart:r,onFinish:i}=n||{};r&&(a.onStart=r),i&&(a.onFinish=i)}}}));const d={scrollY:"scroll",scrollX:"scroll",scrollTop:"scroll",scrollLeft:"scroll",value:"input",currentTime:"timeupdate"},g="data-anim-controller-ref",M="data-anim-controlled-by",w="data-anim-trigger-group",b="data-anim-trigger-config";function v(){const t=document.querySelectorAll(`[${g}], [${M}], [${w}], [data-anim-init]`);t.forEach((t=>{let e=t.dataset.animControllerRef,n=t.dataset.animControlledBy;["scrollTop","scrollY","scrollLeft","scrollX"].includes(n)?(e||(e="window"),t.setAttribute(g,e),"window"===e?("scrollLeft"===n&&t.setAttribute(M,"scrollX"),"scrollTop"===n&&t.setAttribute(M,"scrollY")):("scrollX"===n&&t.setAttribute(M,"scrollLeft"),"scrollY"===n&&t.setAttribute(M,"scrollTop"))):e&&"window"!==e&&!n?t.setAttribute(M,"value"):e||t.dataset.animTriggerGroup||t.setAttribute(w,"default")})),t.forEach((t=>{const e=t.dataset.animControllerRef,n=t.dataset.animControlledBy,o=t.dataset.animListen;if(e||n){const t=e&&"window"!==e?document.querySelector(e):window;if(!t)return console.warn("Missing controller:",e);const a=`${e||"window"}:${n||"value"}`;if(!m.has(a)){const r=new c((()=>parseFloat(t[n||"value"]||0)),{elementSelector:`[${g}="${e||""}"][${M}="${n}"]`});m.set(a,r);const i=o||d[n||"value"]||"input";t.addEventListener(i,(()=>r.updateTimeline()))}}else if(t.dataset.animTriggerGroup){const e=t.dataset.animTriggerGroup;m.has(e)||p(e,{configSelector:`[${w}="${e}"][${b}]`,elementSelector:`[${w}="${e}"]:not([${b}])`})}else;}))}f.play=f.trigger,f.setProgress=f.seek,f.refresh=v,f.getAnimator=t=>m.get(t),f.createTimedAnimator=p,document.addEventListener("DOMContentLoaded",v);return{...u,...f}}();