diff --git a/CHANGELOG.md b/CHANGELOG.md index cbf5fc4..e3946f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project are documented in this file. This project follows Semantic Versioning. +## [0.1.4] - 2026-02-16 + +### Fixed + +- Fixed paused animation behavior when calling `start()` again. It now resumes from the paused point instead of jumping directly to the final value. + ## [0.1.2] - 2026-02-16 ### Changed diff --git a/README.md b/README.md index f18691c..6e6979b 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ counters.update([100, 250, 999]); ### Instância (1 elemento) -Métodos: `start()`, `pause()`, `resume()`, `stop()`, `reset()`, `set(value)`, `update(nextEnd, nextOptions?)`, `destroy()` +Métodos: `start()` (inicia ou retoma se pausado), `pause()`, `resume()`, `stop()`, `reset()`, `set(value)`, `update(nextEnd, nextOptions?)`, `destroy()` Getters: `value`, `running`, `paused` diff --git a/dist/counterup.esm.js b/dist/counterup.esm.js index cc0cf8d..a91e8e5 100644 --- a/dist/counterup.esm.js +++ b/dist/counterup.esm.js @@ -1,4 +1,4 @@ -/* @nullsablex/counter-up v0.1.3 | Author: NullSablex | https://github.com/NullSablex/counter-up.git | MIT License */ +/* @nullsablex/counter-up v0.1.4 | Author: NullSablex | https://github.com/NullSablex/counter-up.git | MIT License */ const easings = { linear: (t) => t, easeInOutQuad: (t) => (t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2), @@ -177,6 +177,12 @@ function createCounterInstance(element, userOptions = {}, index = 0) { } function start() { + if (state.isPaused) { + return resume(); + } + if (state.isRunning) { + return api; + } return play(options.start, options.end); } @@ -185,6 +191,8 @@ function createCounterInstance(element, userOptions = {}, index = 0) { cancelFrame(); state.isRunning = false; state.isPaused = true; + // Keep elapsed progress, but force startTime recalculation on resume. + state.startTime = null; return api; } @@ -192,6 +200,8 @@ function createCounterInstance(element, userOptions = {}, index = 0) { if (!state.isPaused || state.destroyed) return api; state.isRunning = true; state.isPaused = false; + // Re-anchor startTime using the saved elapsed time. + state.startTime = null; state.rafId = requestAnimationFrame(animate); return api; } diff --git a/dist/counterup.esm.min.js b/dist/counterup.esm.min.js index 042f299..9d76106 100644 --- a/dist/counterup.esm.min.js +++ b/dist/counterup.esm.min.js @@ -1,2 +1,4 @@ -/* @nullsablex/counter-up v0.1.3 | Author: NullSablex | https://github.com/NullSablex/counter-up.git | MIT License */ -const easings={linear:(t)=>t,easeInOutQuad:(t)=>(t<0.5?2*t*t:1-Math.pow(-2*t+2,2)/2),easeOutCubic:(t)=>1-Math.pow(1-t,3),};const defaultOptions={start:0,end:100,duration:2000,decimals:0,prefix:"",suffix:"",locale:"pt-BR",useGrouping:true,easing:"easeOutCubic",formatter:null,autostart:true,onUpdate:null,onComplete:null,};function toNumber(value,fallback=0){const n=Number(value);return Number.isFinite(n)?n:fallback;}function normalizeOptions(options){const normalized={...options,start:toNumber(options.start,0),end:toNumber(options.end,100),duration:Math.max(0,toNumber(options.duration,2000)),decimals:Math.max(0,Math.floor(toNumber(options.decimals,0))),};normalized.easingFn=typeof normalized.easing==="function"?normalized.easing:easings[normalized.easing]||easings.easeOutCubic;return normalized;}function formatNumber(value,options){if(typeof options.formatter==="function"){return options.formatter(value,options.element,options.index);}const formatted=new Intl.NumberFormat(options.locale,{minimumFractionDigits:options.decimals,maximumFractionDigits:options.decimals,useGrouping:options.useGrouping,}).format(value);return`${options.prefix}${formatted}${options.suffix}`;}function isElement(target){return typeof Element!=="undefined"&&target instanceof Element;}function resolveElements(target){if(typeof target==="string"){return Array.from(document.querySelectorAll(target));}if(isElement(target)){return[target];}if(Array.isArray(target)){return target.filter(isElement);}if(typeof NodeList!=="undefined"&&(target instanceof NodeList||(typeof HTMLCollection!=="undefined"&&target instanceof HTMLCollection))){return Array.from(target).filter(isElement);}return[];}function createCounterInstance(element,userOptions={},index=0){if(!element){throw new Error("counterUp: target element not found.");}let options=normalizeOptions({...defaultOptions,...userOptions,element,index,});const state={value:options.start,from:options.start,to:options.end,elapsed:0,startTime:null,rafId:null,isRunning:false,isPaused:false,destroyed:false,};function render(value,notify=true){state.value=value;element.textContent=formatNumber(value,options);if(notify&&typeof options.onUpdate==="function"){options.onUpdate(value,element,index);}}function cancelFrame(){if(state.rafId!==null){cancelAnimationFrame(state.rafId);state.rafId=null;}}function animate(timestamp){if(!state.isRunning||state.destroyed)return;if(state.startTime===null){state.startTime=timestamp-state.elapsed;}state.elapsed=timestamp-state.startTime;const progress=options.duration===0?1:Math.min(state.elapsed/options.duration,1);const eased=options.easingFn(progress);const nextValue=state.from+(state.to-state.from)*eased;render(nextValue);if(progress<1){state.rafId=requestAnimationFrame(animate);return;}state.isRunning=false;state.isPaused=false;state.elapsed=0;state.startTime=null;render(state.to);if(typeof options.onComplete==="function"){options.onComplete(state.to,element,index);}}function play(from,to){if(state.destroyed)return api;cancelFrame();state.from=toNumber(from,state.value);state.to=toNumber(to,state.to);state.elapsed=0;state.startTime=null;state.isPaused=false;state.isRunning=true;state.rafId=requestAnimationFrame(animate);return api;}function stop(){cancelFrame();state.isRunning=false;state.isPaused=false;state.elapsed=0;state.startTime=null;return api;}function start(){return play(options.start,options.end);}function pause(){if(!state.isRunning)return api;cancelFrame();state.isRunning=false;state.isPaused=true;return api;}function resume(){if(!state.isPaused||state.destroyed)return api;state.isRunning=true;state.isPaused=false;state.rafId=requestAnimationFrame(animate);return api;}function reset(){stop();render(options.start,false);return api;}function set(value){const nextValue=toNumber(value,state.value);stop();state.from=nextValue;state.to=nextValue;render(nextValue);return api;}function update(nextEnd,nextOptions={}){if(state.destroyed)return api;options=normalizeOptions({...options,...nextOptions,element,index,start:nextOptions.start===undefined?state.value:toNumber(nextOptions.start),end:toNumber(nextEnd,options.end),});return play(options.start,options.end);}function destroy(){stop();state.destroyed=true;}const api={start,stop,pause,resume,reset,set,update,destroy,get value(){return state.value;},get running(){return state.isRunning;},get paused(){return state.isPaused;},};render(options.start,false);if(options.autostart){start();}return api;}function createGroupInstance(elements,userOptions){const instances=elements.map((element,index)=>createCounterInstance(element,userOptions,index));const api={start(){instances.forEach((instance)=>instance.start());return api;},stop(){instances.forEach((instance)=>instance.stop());return api;},pause(){instances.forEach((instance)=>instance.pause());return api;},resume(){instances.forEach((instance)=>instance.resume());return api;},reset(){instances.forEach((instance)=>instance.reset());return api;},set(value){if(Array.isArray(value)){instances.forEach((instance,index)=>{instance.set(value[index]??value[value.length-1]??0);});return api;}instances.forEach((instance)=>instance.set(value));return api;},update(nextEnd,nextOptions={}){if(Array.isArray(nextEnd)){instances.forEach((instance,index)=>{instance.update(nextEnd[index]??nextEnd[nextEnd.length-1]??0,nextOptions);});return api;}instances.forEach((instance)=>instance.update(nextEnd,nextOptions));return api;},destroy(){instances.forEach((instance)=>instance.destroy());return api;},get values(){return instances.map((instance)=>instance.value);},get running(){return instances.some((instance)=>instance.running);},get paused(){return instances.some((instance)=>instance.paused);},get count(){return instances.length;},};return api;}export function counterUp(target,userOptions={}){const elements=resolveElements(target);if(elements.length===0){throw new Error("counterUp: target element not found.");}if(elements.length===1){return createCounterInstance(elements[0],userOptions,0);}return createGroupInstance(elements,userOptions);}export default counterUp; \ No newline at end of file +/* @nullsablex/counter-up v0.1.4 | Author: NullSablex | https://github.com/NullSablex/counter-up.git | MIT License */ +const easings={linear:(t)=>t,easeInOutQuad:(t)=>(t<0.5?2*t*t:1-Math.pow(-2*t+2,2)/2),easeOutCubic:(t)=>1-Math.pow(1-t,3),};const defaultOptions={start:0,end:100,duration:2000,decimals:0,prefix:"",suffix:"",locale:"pt-BR",useGrouping:true,easing:"easeOutCubic",formatter:null,autostart:true,onUpdate:null,onComplete:null,};function toNumber(value,fallback=0){const n=Number(value);return Number.isFinite(n)?n:fallback;}function normalizeOptions(options){const normalized={...options,start:toNumber(options.start,0),end:toNumber(options.end,100),duration:Math.max(0,toNumber(options.duration,2000)),decimals:Math.max(0,Math.floor(toNumber(options.decimals,0))),};normalized.easingFn=typeof normalized.easing==="function"?normalized.easing:easings[normalized.easing]||easings.easeOutCubic;return normalized;}function formatNumber(value,options){if(typeof options.formatter==="function"){return options.formatter(value,options.element,options.index);}const formatted=new Intl.NumberFormat(options.locale,{minimumFractionDigits:options.decimals,maximumFractionDigits:options.decimals,useGrouping:options.useGrouping,}).format(value);return`${options.prefix}${formatted}${options.suffix}`;}function isElement(target){return typeof Element!=="undefined"&&target instanceof Element;}function resolveElements(target){if(typeof target==="string"){return Array.from(document.querySelectorAll(target));}if(isElement(target)){return[target];}if(Array.isArray(target)){return target.filter(isElement);}if(typeof NodeList!=="undefined"&&(target instanceof NodeList||(typeof HTMLCollection!=="undefined"&&target instanceof HTMLCollection))){return Array.from(target).filter(isElement);}return[];}function createCounterInstance(element,userOptions={},index=0){if(!element){throw new Error("counterUp: target element not found.");}let options=normalizeOptions({...defaultOptions,...userOptions,element,index,});const state={value:options.start,from:options.start,to:options.end,elapsed:0,startTime:null,rafId:null,isRunning:false,isPaused:false,destroyed:false,};function render(value,notify=true){state.value=value;element.textContent=formatNumber(value,options);if(notify&&typeof options.onUpdate==="function"){options.onUpdate(value,element,index);}}function cancelFrame(){if(state.rafId!==null){cancelAnimationFrame(state.rafId);state.rafId=null;}}function animate(timestamp){if(!state.isRunning||state.destroyed)return;if(state.startTime===null){state.startTime=timestamp-state.elapsed;}state.elapsed=timestamp-state.startTime;const progress=options.duration===0?1:Math.min(state.elapsed/options.duration,1);const eased=options.easingFn(progress);const nextValue=state.from+(state.to-state.from)*eased;render(nextValue);if(progress<1){state.rafId=requestAnimationFrame(animate);return;}state.isRunning=false;state.isPaused=false;state.elapsed=0;state.startTime=null;render(state.to);if(typeof options.onComplete==="function"){options.onComplete(state.to,element,index);}}function play(from,to){if(state.destroyed)return api;cancelFrame();state.from=toNumber(from,state.value);state.to=toNumber(to,state.to);state.elapsed=0;state.startTime=null;state.isPaused=false;state.isRunning=true;state.rafId=requestAnimationFrame(animate);return api;}function stop(){cancelFrame();state.isRunning=false;state.isPaused=false;state.elapsed=0;state.startTime=null;return api;}function start(){if(state.isPaused){return resume();}if(state.isRunning){return api;}return play(options.start,options.end);}function pause(){if(!state.isRunning)return api;cancelFrame();state.isRunning=false;state.isPaused=true; +state.startTime=null;return api;}function resume(){if(!state.isPaused||state.destroyed)return api;state.isRunning=true;state.isPaused=false; +state.startTime=null;state.rafId=requestAnimationFrame(animate);return api;}function reset(){stop();render(options.start,false);return api;}function set(value){const nextValue=toNumber(value,state.value);stop();state.from=nextValue;state.to=nextValue;render(nextValue);return api;}function update(nextEnd,nextOptions={}){if(state.destroyed)return api;options=normalizeOptions({...options,...nextOptions,element,index,start:nextOptions.start===undefined?state.value:toNumber(nextOptions.start),end:toNumber(nextEnd,options.end),});return play(options.start,options.end);}function destroy(){stop();state.destroyed=true;}const api={start,stop,pause,resume,reset,set,update,destroy,get value(){return state.value;},get running(){return state.isRunning;},get paused(){return state.isPaused;},};render(options.start,false);if(options.autostart){start();}return api;}function createGroupInstance(elements,userOptions){const instances=elements.map((element,index)=>createCounterInstance(element,userOptions,index));const api={start(){instances.forEach((instance)=>instance.start());return api;},stop(){instances.forEach((instance)=>instance.stop());return api;},pause(){instances.forEach((instance)=>instance.pause());return api;},resume(){instances.forEach((instance)=>instance.resume());return api;},reset(){instances.forEach((instance)=>instance.reset());return api;},set(value){if(Array.isArray(value)){instances.forEach((instance,index)=>{instance.set(value[index]??value[value.length-1]??0);});return api;}instances.forEach((instance)=>instance.set(value));return api;},update(nextEnd,nextOptions={}){if(Array.isArray(nextEnd)){instances.forEach((instance,index)=>{instance.update(nextEnd[index]??nextEnd[nextEnd.length-1]??0,nextOptions);});return api;}instances.forEach((instance)=>instance.update(nextEnd,nextOptions));return api;},destroy(){instances.forEach((instance)=>instance.destroy());return api;},get values(){return instances.map((instance)=>instance.value);},get running(){return instances.some((instance)=>instance.running);},get paused(){return instances.some((instance)=>instance.paused);},get count(){return instances.length;},};return api;}export function counterUp(target,userOptions={}){const elements=resolveElements(target);if(elements.length===0){throw new Error("counterUp: target element not found.");}if(elements.length===1){return createCounterInstance(elements[0],userOptions,0);}return createGroupInstance(elements,userOptions);}export default counterUp; \ No newline at end of file diff --git a/dist/counterup.umd.js b/dist/counterup.umd.js index 2c7c264..7ea3ef5 100644 --- a/dist/counterup.umd.js +++ b/dist/counterup.umd.js @@ -1,4 +1,4 @@ -/* @nullsablex/counter-up v0.1.3 | Author: NullSablex | https://github.com/NullSablex/counter-up.git | MIT License */ +/* @nullsablex/counter-up v0.1.4 | Author: NullSablex | https://github.com/NullSablex/counter-up.git | MIT License */ (function (global, factory) { if (typeof module === "object" && typeof module.exports === "object") { module.exports = factory(); @@ -185,6 +185,12 @@ } function start() { + if (state.isPaused) { + return resume(); + } + if (state.isRunning) { + return api; + } return play(options.start, options.end); } @@ -193,6 +199,8 @@ cancelFrame(); state.isRunning = false; state.isPaused = true; + // Keep elapsed progress, but force startTime recalculation on resume. + state.startTime = null; return api; } @@ -200,6 +208,8 @@ if (!state.isPaused || state.destroyed) return api; state.isRunning = true; state.isPaused = false; + // Re-anchor startTime using the saved elapsed time. + state.startTime = null; state.rafId = requestAnimationFrame(animate); return api; } diff --git a/dist/counterup.umd.min.js b/dist/counterup.umd.min.js index 93844e7..cdf9af7 100644 --- a/dist/counterup.umd.min.js +++ b/dist/counterup.umd.min.js @@ -1,2 +1,4 @@ -/* @nullsablex/counter-up v0.1.3 | Author: NullSablex | https://github.com/NullSablex/counter-up.git | MIT License */ -(function(global,factory){if(typeof module==="object"&&typeof module.exports==="object"){module.exports=factory();}else{global.CounterUp=factory();}})(typeof window!=="undefined"?window:this,function(){"use strict";const easings={linear:(t)=>t,easeInOutQuad:(t)=>(t<0.5?2*t*t:1-Math.pow(-2*t+2,2)/2),easeOutCubic:(t)=>1-Math.pow(1-t,3),};const defaultOptions={start:0,end:100,duration:2000,decimals:0,prefix:"",suffix:"",locale:"pt-BR",useGrouping:true,easing:"easeOutCubic",formatter:null,autostart:true,onUpdate:null,onComplete:null,};function toNumber(value,fallback=0){const n=Number(value);return Number.isFinite(n)?n:fallback;}function normalizeOptions(options){const normalized={...options,start:toNumber(options.start,0),end:toNumber(options.end,100),duration:Math.max(0,toNumber(options.duration,2000)),decimals:Math.max(0,Math.floor(toNumber(options.decimals,0))),};normalized.easingFn=typeof normalized.easing==="function"?normalized.easing:easings[normalized.easing]||easings.easeOutCubic;return normalized;}function formatNumber(value,options){if(typeof options.formatter==="function"){return options.formatter(value,options.element,options.index);}const formatted=new Intl.NumberFormat(options.locale,{minimumFractionDigits:options.decimals,maximumFractionDigits:options.decimals,useGrouping:options.useGrouping,}).format(value);return`${options.prefix}${formatted}${options.suffix}`;}function isElement(target){return typeof Element!=="undefined"&&target instanceof Element;}function resolveElements(target){if(typeof target==="string"){return Array.from(document.querySelectorAll(target));}if(isElement(target)){return[target];}if(Array.isArray(target)){return target.filter(isElement);}if(typeof NodeList!=="undefined"&&(target instanceof NodeList||(typeof HTMLCollection!=="undefined"&&target instanceof HTMLCollection))){return Array.from(target).filter(isElement);}return[];}function createCounterInstance(element,userOptions={},index=0){if(!element){throw new Error("counterUp: target element not found.");}let options=normalizeOptions({...defaultOptions,...userOptions,element,index,});const state={value:options.start,from:options.start,to:options.end,elapsed:0,startTime:null,rafId:null,isRunning:false,isPaused:false,destroyed:false,};function render(value,notify=true){state.value=value;element.textContent=formatNumber(value,options);if(notify&&typeof options.onUpdate==="function"){options.onUpdate(value,element,index);}}function cancelFrame(){if(state.rafId!==null){cancelAnimationFrame(state.rafId);state.rafId=null;}}function animate(timestamp){if(!state.isRunning||state.destroyed)return;if(state.startTime===null){state.startTime=timestamp-state.elapsed;}state.elapsed=timestamp-state.startTime;const progress=options.duration===0?1:Math.min(state.elapsed/options.duration,1);const eased=options.easingFn(progress);const nextValue=state.from+(state.to-state.from)*eased;render(nextValue);if(progress<1){state.rafId=requestAnimationFrame(animate);return;}state.isRunning=false;state.isPaused=false;state.elapsed=0;state.startTime=null;render(state.to);if(typeof options.onComplete==="function"){options.onComplete(state.to,element,index);}}function play(from,to){if(state.destroyed)return api;cancelFrame();state.from=toNumber(from,state.value);state.to=toNumber(to,state.to);state.elapsed=0;state.startTime=null;state.isPaused=false;state.isRunning=true;state.rafId=requestAnimationFrame(animate);return api;}function stop(){cancelFrame();state.isRunning=false;state.isPaused=false;state.elapsed=0;state.startTime=null;return api;}function start(){return play(options.start,options.end);}function pause(){if(!state.isRunning)return api;cancelFrame();state.isRunning=false;state.isPaused=true;return api;}function resume(){if(!state.isPaused||state.destroyed)return api;state.isRunning=true;state.isPaused=false;state.rafId=requestAnimationFrame(animate);return api;}function reset(){stop();render(options.start,false);return api;}function set(value){const nextValue=toNumber(value,state.value);stop();state.from=nextValue;state.to=nextValue;render(nextValue);return api;}function update(nextEnd,nextOptions={}){if(state.destroyed)return api;options=normalizeOptions({...options,...nextOptions,element,index,start:nextOptions.start===undefined?state.value:toNumber(nextOptions.start),end:toNumber(nextEnd,options.end),});return play(options.start,options.end);}function destroy(){stop();state.destroyed=true;}const api={start,stop,pause,resume,reset,set,update,destroy,get value(){return state.value;},get running(){return state.isRunning;},get paused(){return state.isPaused;},};render(options.start,false);if(options.autostart){start();}return api;}function createGroupInstance(elements,userOptions){const instances=elements.map((element,index)=>createCounterInstance(element,userOptions,index));const api={start(){instances.forEach((instance)=>instance.start());return api;},stop(){instances.forEach((instance)=>instance.stop());return api;},pause(){instances.forEach((instance)=>instance.pause());return api;},resume(){instances.forEach((instance)=>instance.resume());return api;},reset(){instances.forEach((instance)=>instance.reset());return api;},set(value){if(Array.isArray(value)){instances.forEach((instance,index)=>{instance.set(value[index]??value[value.length-1]??0);});return api;}instances.forEach((instance)=>instance.set(value));return api;},update(nextEnd,nextOptions={}){if(Array.isArray(nextEnd)){instances.forEach((instance,index)=>{instance.update(nextEnd[index]??nextEnd[nextEnd.length-1]??0,nextOptions);});return api;}instances.forEach((instance)=>instance.update(nextEnd,nextOptions));return api;},destroy(){instances.forEach((instance)=>instance.destroy());return api;},get values(){return instances.map((instance)=>instance.value);},get running(){return instances.some((instance)=>instance.running);},get paused(){return instances.some((instance)=>instance.paused);},get count(){return instances.length;},};return api;}function counterUp(target,userOptions={}){const elements=resolveElements(target);if(elements.length===0){throw new Error("counterUp: target element not found.");}if(elements.length===1){return createCounterInstance(elements[0],userOptions,0);}return createGroupInstance(elements,userOptions);}return{counterUp:counterUp,default:counterUp};}); \ No newline at end of file +/* @nullsablex/counter-up v0.1.4 | Author: NullSablex | https://github.com/NullSablex/counter-up.git | MIT License */ +(function(global,factory){if(typeof module==="object"&&typeof module.exports==="object"){module.exports=factory();}else{global.CounterUp=factory();}})(typeof window!=="undefined"?window:this,function(){"use strict";const easings={linear:(t)=>t,easeInOutQuad:(t)=>(t<0.5?2*t*t:1-Math.pow(-2*t+2,2)/2),easeOutCubic:(t)=>1-Math.pow(1-t,3),};const defaultOptions={start:0,end:100,duration:2000,decimals:0,prefix:"",suffix:"",locale:"pt-BR",useGrouping:true,easing:"easeOutCubic",formatter:null,autostart:true,onUpdate:null,onComplete:null,};function toNumber(value,fallback=0){const n=Number(value);return Number.isFinite(n)?n:fallback;}function normalizeOptions(options){const normalized={...options,start:toNumber(options.start,0),end:toNumber(options.end,100),duration:Math.max(0,toNumber(options.duration,2000)),decimals:Math.max(0,Math.floor(toNumber(options.decimals,0))),};normalized.easingFn=typeof normalized.easing==="function"?normalized.easing:easings[normalized.easing]||easings.easeOutCubic;return normalized;}function formatNumber(value,options){if(typeof options.formatter==="function"){return options.formatter(value,options.element,options.index);}const formatted=new Intl.NumberFormat(options.locale,{minimumFractionDigits:options.decimals,maximumFractionDigits:options.decimals,useGrouping:options.useGrouping,}).format(value);return`${options.prefix}${formatted}${options.suffix}`;}function isElement(target){return typeof Element!=="undefined"&&target instanceof Element;}function resolveElements(target){if(typeof target==="string"){return Array.from(document.querySelectorAll(target));}if(isElement(target)){return[target];}if(Array.isArray(target)){return target.filter(isElement);}if(typeof NodeList!=="undefined"&&(target instanceof NodeList||(typeof HTMLCollection!=="undefined"&&target instanceof HTMLCollection))){return Array.from(target).filter(isElement);}return[];}function createCounterInstance(element,userOptions={},index=0){if(!element){throw new Error("counterUp: target element not found.");}let options=normalizeOptions({...defaultOptions,...userOptions,element,index,});const state={value:options.start,from:options.start,to:options.end,elapsed:0,startTime:null,rafId:null,isRunning:false,isPaused:false,destroyed:false,};function render(value,notify=true){state.value=value;element.textContent=formatNumber(value,options);if(notify&&typeof options.onUpdate==="function"){options.onUpdate(value,element,index);}}function cancelFrame(){if(state.rafId!==null){cancelAnimationFrame(state.rafId);state.rafId=null;}}function animate(timestamp){if(!state.isRunning||state.destroyed)return;if(state.startTime===null){state.startTime=timestamp-state.elapsed;}state.elapsed=timestamp-state.startTime;const progress=options.duration===0?1:Math.min(state.elapsed/options.duration,1);const eased=options.easingFn(progress);const nextValue=state.from+(state.to-state.from)*eased;render(nextValue);if(progress<1){state.rafId=requestAnimationFrame(animate);return;}state.isRunning=false;state.isPaused=false;state.elapsed=0;state.startTime=null;render(state.to);if(typeof options.onComplete==="function"){options.onComplete(state.to,element,index);}}function play(from,to){if(state.destroyed)return api;cancelFrame();state.from=toNumber(from,state.value);state.to=toNumber(to,state.to);state.elapsed=0;state.startTime=null;state.isPaused=false;state.isRunning=true;state.rafId=requestAnimationFrame(animate);return api;}function stop(){cancelFrame();state.isRunning=false;state.isPaused=false;state.elapsed=0;state.startTime=null;return api;}function start(){if(state.isPaused){return resume();}if(state.isRunning){return api;}return play(options.start,options.end);}function pause(){if(!state.isRunning)return api;cancelFrame();state.isRunning=false;state.isPaused=true; +state.startTime=null;return api;}function resume(){if(!state.isPaused||state.destroyed)return api;state.isRunning=true;state.isPaused=false; +state.startTime=null;state.rafId=requestAnimationFrame(animate);return api;}function reset(){stop();render(options.start,false);return api;}function set(value){const nextValue=toNumber(value,state.value);stop();state.from=nextValue;state.to=nextValue;render(nextValue);return api;}function update(nextEnd,nextOptions={}){if(state.destroyed)return api;options=normalizeOptions({...options,...nextOptions,element,index,start:nextOptions.start===undefined?state.value:toNumber(nextOptions.start),end:toNumber(nextEnd,options.end),});return play(options.start,options.end);}function destroy(){stop();state.destroyed=true;}const api={start,stop,pause,resume,reset,set,update,destroy,get value(){return state.value;},get running(){return state.isRunning;},get paused(){return state.isPaused;},};render(options.start,false);if(options.autostart){start();}return api;}function createGroupInstance(elements,userOptions){const instances=elements.map((element,index)=>createCounterInstance(element,userOptions,index));const api={start(){instances.forEach((instance)=>instance.start());return api;},stop(){instances.forEach((instance)=>instance.stop());return api;},pause(){instances.forEach((instance)=>instance.pause());return api;},resume(){instances.forEach((instance)=>instance.resume());return api;},reset(){instances.forEach((instance)=>instance.reset());return api;},set(value){if(Array.isArray(value)){instances.forEach((instance,index)=>{instance.set(value[index]??value[value.length-1]??0);});return api;}instances.forEach((instance)=>instance.set(value));return api;},update(nextEnd,nextOptions={}){if(Array.isArray(nextEnd)){instances.forEach((instance,index)=>{instance.update(nextEnd[index]??nextEnd[nextEnd.length-1]??0,nextOptions);});return api;}instances.forEach((instance)=>instance.update(nextEnd,nextOptions));return api;},destroy(){instances.forEach((instance)=>instance.destroy());return api;},get values(){return instances.map((instance)=>instance.value);},get running(){return instances.some((instance)=>instance.running);},get paused(){return instances.some((instance)=>instance.paused);},get count(){return instances.length;},};return api;}function counterUp(target,userOptions={}){const elements=resolveElements(target);if(elements.length===0){throw new Error("counterUp: target element not found.");}if(elements.length===1){return createCounterInstance(elements[0],userOptions,0);}return createGroupInstance(elements,userOptions);}return{counterUp:counterUp,default:counterUp};}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5134679..3542758 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@nullsablex/counter-up", - "version": "0.1.3", + "version": "0.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@nullsablex/counter-up", - "version": "0.1.3", + "version": "0.1.4", "license": "MIT" } } diff --git a/package.json b/package.json index cb6c761..8acdebe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nullsablex/counter-up", - "version": "0.1.3", + "version": "0.1.4", "description": "Biblioteca JS pura para animação de contadores numéricos", "author": "NullSablex", "repository": { diff --git a/src/counterup.js b/src/counterup.js index 5dbaa26..90e6e42 100644 --- a/src/counterup.js +++ b/src/counterup.js @@ -176,6 +176,12 @@ function createCounterInstance(element, userOptions = {}, index = 0) { } function start() { + if (state.isPaused) { + return resume(); + } + if (state.isRunning) { + return api; + } return play(options.start, options.end); } @@ -184,6 +190,8 @@ function createCounterInstance(element, userOptions = {}, index = 0) { cancelFrame(); state.isRunning = false; state.isPaused = true; + // Keep elapsed progress, but force startTime recalculation on resume. + state.startTime = null; return api; } @@ -191,6 +199,8 @@ function createCounterInstance(element, userOptions = {}, index = 0) { if (!state.isPaused || state.destroyed) return api; state.isRunning = true; state.isPaused = false; + // Re-anchor startTime using the saved elapsed time. + state.startTime = null; state.rafId = requestAnimationFrame(animate); return api; }