litecanvas 0.84.0 → 0.84.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/dist.dev.js CHANGED
@@ -33,10 +33,10 @@
33
33
 
34
34
  // src/index.js
35
35
  function litecanvas(settings = {}) {
36
- const root = globalThis, math = Math, TWO_PI = math.PI * 2, raf = requestAnimationFrame, _browserEventListeners = [], on = (elem, evt, callback) => {
36
+ const root = window, math = Math, TWO_PI = math.PI * 2, raf = requestAnimationFrame, _browserEventListeners = [], on = (elem, evt, callback) => {
37
37
  elem.addEventListener(evt, callback, false);
38
38
  _browserEventListeners.push(() => elem.removeEventListener(evt, callback, false));
39
- }, zzfx = setupZzFX(root), isNumber = Number.isFinite, defaults = {
39
+ }, isNumber = Number.isFinite, zzfx = setupZzFX(root), defaults = {
40
40
  width: null,
41
41
  height: null,
42
42
  autoscale: true,
@@ -556,8 +556,11 @@
556
556
  * @see https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
557
557
  */
558
558
  paint(width, height, drawing, options = {}) {
559
- DEV: assert(isNumber(width), "paint: 1st param must be a number");
560
- DEV: assert(isNumber(height), "paint: 2nd param must be a number");
559
+ DEV: assert(isNumber(width) && width >= 1, "paint: 1st param must be a positive number");
560
+ DEV: assert(
561
+ isNumber(height) && height >= 1,
562
+ "paint: 2nd param must be a positive number"
563
+ );
561
564
  DEV: assert(
562
565
  "function" === typeof drawing || Array.isArray(drawing),
563
566
  "paint: 3rd param must be a function or array"
@@ -566,12 +569,16 @@
566
569
  options && !options.scale || isNumber(options.scale),
567
570
  "paint: 4th param (options.scale) must be a number"
568
571
  );
572
+ DEV: assert(
573
+ options && !options.canvas || options.canvas instanceof OffscreenCanvas,
574
+ "paint: 4th param (options.canvas) must be an OffscreenCanvas"
575
+ );
569
576
  const canvas = options.canvas || new OffscreenCanvas(1, 1), scale = options.scale || 1, contextOriginal = _ctx;
570
577
  canvas.width = width * scale;
571
578
  canvas.height = height * scale;
572
579
  _ctx = canvas.getContext("2d");
573
580
  _ctx.scale(scale, scale);
574
- if (drawing.push) {
581
+ if (Array.isArray(drawing)) {
575
582
  let x = 0, y = 0;
576
583
  _ctx.imageSmoothingEnabled = false;
577
584
  for (const str of drawing) {
@@ -748,7 +755,10 @@
748
755
  );
749
756
  DEV: assert(isNumber(pitchSlide), "sfx: 2nd param must be a number");
750
757
  DEV: assert(isNumber(volumeFactor), "sfx: 3rd param must be a number");
751
- if (root.zzfxV <= 0 || navigator.userActivation && !navigator.userActivation.hasBeenActive) {
758
+ if (
759
+ // @ts-ignore
760
+ root.zzfxV <= 0 || navigator.userActivation && !navigator.userActivation.hasBeenActive
761
+ ) {
752
762
  return false;
753
763
  }
754
764
  zzfxParams = zzfxParams || _defaultSound;
@@ -776,9 +786,7 @@
776
786
  *
777
787
  * @returns {HTMLCanvasElement}
778
788
  */
779
- canvas() {
780
- return _canvas;
781
- },
789
+ canvas: () => _canvas,
782
790
  /**
783
791
  * Prepares a plugin to be loaded
784
792
  *
@@ -887,7 +895,7 @@
887
895
  * @returns {any}
888
896
  */
889
897
  stat(n) {
890
- DEV: assert(isNumber(n) && n >= 0, "stat: 1st param must be a positive number");
898
+ DEV: assert(isNumber(n) && n >= 0, "stat: 1st param must be a number");
891
899
  const list = [
892
900
  // 0
893
901
  settings,
@@ -906,6 +914,7 @@
906
914
  // 7
907
915
  _timeScale,
908
916
  // 8
917
+ // @ts-ignore
909
918
  root.zzfxV || 1,
910
919
  // 9
911
920
  _rngSeed,
@@ -935,6 +944,7 @@
935
944
  }
936
945
  delete root.ENGINE;
937
946
  }
947
+ _initialized = false;
938
948
  }
939
949
  };
940
950
  for (const k of "PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp".split(",")) {
@@ -1199,7 +1209,6 @@
1199
1209
  }
1200
1210
  _canvas = _canvas || document.createElement("canvas");
1201
1211
  DEV: assert(_canvas && _canvas.tagName === "CANVAS", "Invalid canvas element");
1202
- instance.def("CANVAS", _canvas);
1203
1212
  _ctx = _canvas.getContext("2d");
1204
1213
  on(_canvas, "click", () => root.focus());
1205
1214
  _canvas.style = "";
@@ -1224,8 +1233,6 @@
1224
1233
  const width = settings.width || root.innerWidth, height = settings.height || settings.width || root.innerHeight;
1225
1234
  instance.def("W", _canvas.width = width);
1226
1235
  instance.def("H", _canvas.height = height);
1227
- instance.def("CX", instance.W / 2);
1228
- instance.def("CY", instance.H / 2);
1229
1236
  if (settings.autoscale) {
1230
1237
  if (!_canvas.style.display) {
1231
1238
  _canvas.style.display = "block";
package/dist/dist.js CHANGED
@@ -28,10 +28,10 @@
28
28
 
29
29
  // src/index.js
30
30
  function litecanvas(settings = {}) {
31
- const root = globalThis, math = Math, TWO_PI = math.PI * 2, raf = requestAnimationFrame, _browserEventListeners = [], on = (elem, evt, callback) => {
31
+ const root = window, math = Math, TWO_PI = math.PI * 2, raf = requestAnimationFrame, _browserEventListeners = [], on = (elem, evt, callback) => {
32
32
  elem.addEventListener(evt, callback, false);
33
33
  _browserEventListeners.push(() => elem.removeEventListener(evt, callback, false));
34
- }, zzfx = setupZzFX(root), isNumber = Number.isFinite, defaults = {
34
+ }, isNumber = Number.isFinite, zzfx = setupZzFX(root), defaults = {
35
35
  width: null,
36
36
  height: null,
37
37
  autoscale: true,
@@ -417,7 +417,7 @@
417
417
  canvas.height = height * scale;
418
418
  _ctx = canvas.getContext("2d");
419
419
  _ctx.scale(scale, scale);
420
- if (drawing.push) {
420
+ if (Array.isArray(drawing)) {
421
421
  let x = 0, y = 0;
422
422
  _ctx.imageSmoothingEnabled = false;
423
423
  for (const str of drawing) {
@@ -561,7 +561,10 @@
561
561
  * @see https://github.com/KilledByAPixel/ZzFX
562
562
  */
563
563
  sfx(zzfxParams, pitchSlide = 0, volumeFactor = 1) {
564
- if (root.zzfxV <= 0 || navigator.userActivation && !navigator.userActivation.hasBeenActive) {
564
+ if (
565
+ // @ts-ignore
566
+ root.zzfxV <= 0 || navigator.userActivation && !navigator.userActivation.hasBeenActive
567
+ ) {
565
568
  return false;
566
569
  }
567
570
  zzfxParams = zzfxParams || _defaultSound;
@@ -588,9 +591,7 @@
588
591
  *
589
592
  * @returns {HTMLCanvasElement}
590
593
  */
591
- canvas() {
592
- return _canvas;
593
- },
594
+ canvas: () => _canvas,
594
595
  /**
595
596
  * Prepares a plugin to be loaded
596
597
  *
@@ -696,6 +697,7 @@
696
697
  // 7
697
698
  _timeScale,
698
699
  // 8
700
+ // @ts-ignore
699
701
  root.zzfxV || 1,
700
702
  // 9
701
703
  _rngSeed,
@@ -725,6 +727,7 @@
725
727
  }
726
728
  delete root.ENGINE;
727
729
  }
730
+ _initialized = false;
728
731
  }
729
732
  };
730
733
  for (const k of "PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp".split(",")) {
@@ -976,7 +979,6 @@
976
979
  _canvas = document.querySelector(settings.canvas);
977
980
  }
978
981
  _canvas = _canvas || document.createElement("canvas");
979
- instance.def("CANVAS", _canvas);
980
982
  _ctx = _canvas.getContext("2d");
981
983
  on(_canvas, "click", () => root.focus());
982
984
  _canvas.style = "";
@@ -989,8 +991,6 @@
989
991
  const width = settings.width || root.innerWidth, height = settings.height || settings.width || root.innerHeight;
990
992
  instance.def("W", _canvas.width = width);
991
993
  instance.def("H", _canvas.height = height);
992
- instance.def("CX", instance.W / 2);
993
- instance.def("CY", instance.H / 2);
994
994
  if (settings.autoscale) {
995
995
  if (!_canvas.style.display) {
996
996
  _canvas.style.display = "block";
package/dist/dist.min.js CHANGED
@@ -1 +1 @@
1
- (()=>{var e=["#111","#6a7799","#aec2c2","#FFF1E8","#e83b3b","#fabc20","#155fd9","#3cbcfc","#327345","#63c64d","#6c2c1f","#ac7c00"];globalThis.litecanvas=function(t={}){let a=globalThis,l=Math,n=2*l.PI,i=requestAnimationFrame,o=[],r=(e,t,a)=>{e.addEventListener(t,a,!1),o.push(()=>e.removeEventListener(t,a,!1))},s=(e=>{let t=new AudioContext;return e.zzfxV=1,(a=1,l=.05,n=220,i=0,o=0,r=.1,s=0,f=1,c=0,d=0,p=0,u=0,h=0,g=0,m=0,w=0,v=0,x=1,b=0,y=0,k=0)=>{let E=Math,z=2*E.PI,C=c*=500*z/44100/44100,T=n*=(1-l+2*l*E.random(l=[]))*z/44100,I=0,P=0,S=0,A=1,L=0,X=0,Y=0,M=k<0?-1:1,H=z*M*k*2/44100,N=E.cos(H),W=E.sin,D=W(H)/4,F=1+D,V=-2*N/F,q=(1-D)/F,B=(1+M*N)/2/F,O=-(M+N)/F,R=0,G=0,$=0,j=0;for(i=44100*i+9,b*=44100,o*=44100,r*=44100,v*=44100,d*=500*z/85766121e6,m*=z/44100,p*=z/44100,u*=44100,h=44100*h|0,a*=.3*e.zzfxV,M=i+b+o+r+v|0;S<M;l[S++]=Y*a)++X%(100*w|0)||(Y=s?1<s?2<s?3<s?W(I*I):E.max(E.min(E.tan(I),1),-1):1-(2*I/z%2+2)%2:1-4*E.abs(E.round(I/z)-I/z):W(I),Y=(h?1-y+y*W(z*S/h):1)*(Y<0?-1:1)*E.abs(Y)**f*(S<i?S/i:S<i+b?1-(S-i)/b*(1-x):S<i+b+o?x:S<M-v?(M-S-v)/r*x:0),Y=v?Y/2+(v>S?0:(S<M-v?1:(M-S)/v)*l[S-v|0]/2/a):Y,k&&(Y=j=B*R+O*(R=G)+B*(G=Y)-q*$-V*($=j))),I+=(H=(n+=c+=d)*E.cos(m*P++))+H*g*W(S**5),A&&++A>u&&(n+=p,T+=p,A=0),!h||++L%h||(n=T,c=C,A=A||1);(a=t.createBuffer(1,M,44100)).getChannelData(0).set(l),(n=t.createBufferSource()).buffer=a,n.connect(t.destination),n.start()}})(a);t=Object.assign({width:null,height:null,autoscale:!0,pixelart:!1,antialias:!1,canvas:null,global:!0,loop:null,tapEvents:!0,keyboardEvents:!0,animate:!0},t);let f=!1,c=[],d,p=1,u,h=.5,g=1,m,w=1/60,v=0,x,b="sans-serif",y=20,k=Date.now(),E=e,z=[.5,0,1750,,,.3,1,,,,600,.1],C={init:null,update:null,draw:null,resized:null,tap:null,untap:null,tapping:null,tapped:null},T={W:0,H:0,T:0,MX:-1,MY:-1,TWO_PI:n,HALF_PI:n/4,lerp:(e,t,a)=>a*(t-e)+e,deg2rad:e=>l.PI/180*e,rad2deg:e=>180/l.PI*e,round:(e,t=0)=>{if(!t)return l.round(e);let a=10**t;return l.round(e*a)/a},clamp:(e,t,a)=>e<t?t:e>a?a:e,wrap:(e,t,a)=>e-(a-t)*l.floor((e-t)/(a-t)),map(e,t,a,l,n,i){let o=(e-t)/(a-t)*(n-l)+l;return i?T.clamp(o,l,n):o},norm:(e,t,a)=>T.map(e,t,a,0,1),wave:(e,t,a,l=Math.sin)=>e+(l(a)+1)/2*(t-e),rand:(e=0,t=1)=>(k=(1664525*k+0x3c6ef35f)%0x100000000)/0x100000000*(t-e)+e,randi:(e=0,t=1)=>l.floor(T.rand(e,t+1)),rseed(e){k=~~e},cls(e){null==e?u.clearRect(0,0,u.canvas.width,u.canvas.height):T.rectfill(0,0,u.canvas.width,u.canvas.height,e)},rect(e,t,a,l,n,i){u.beginPath(),u[i?"roundRect":"rect"](~~e-h,~~t-h,~~a+2*h,~~l+2*h,i),T.stroke(n)},rectfill(e,t,a,l,n,i){u.beginPath(),u[i?"roundRect":"rect"](~~e,~~t,~~a,~~l,i),T.fill(n)},circ(e,t,a,l){u.beginPath(),u.arc(~~e,~~t,~~a,0,n),T.stroke(l)},circfill(e,t,a,l){u.beginPath(),u.arc(~~e,~~t,~~a,0,n),T.fill(l)},line(e,t,a,l,n){u.beginPath();let i=.5*(0!==h&&~~e==~~a),o=.5*(0!==h&&~~t==~~l);u.moveTo(~~e+i,~~t+o),u.lineTo(~~a+i,~~l+o),T.stroke(n)},linewidth(e){u.lineWidth=~~e,h=.5*(0!=~~e%2)},linedash(e,t=0){u.setLineDash(e),u.lineDashOffset=t},text(e,t,a,l=3,n="normal"){u.font=`${n} ${y}px ${b}`,u.fillStyle=E[~~l%E.length],u.fillText(a,~~e,~~t)},textfont(e){b=e},textsize(e){y=e},textalign(e,t){e&&(u.textAlign=e),t&&(u.textBaseline=t)},image(e,t,a){u.drawImage(a,~~e,~~t)},paint(e,t,a,l={}){let n=l.canvas||new OffscreenCanvas(1,1),i=l.scale||1,o=u;if(n.width=e*i,n.height=t*i,(u=n.getContext("2d")).scale(i,i),a.push){let e=0,t=0;for(let l of(u.imageSmoothingEnabled=!1,a)){for(let a of l)" "!==a&&"."!==a&&T.rectfill(e,t,1,1,parseInt(a,16)),e++;t++,e=0}}else a(u);return u=o,n.transferToImageBitmap()},ctx:e=>(e&&(u=e),u),push:()=>u.save(),pop:()=>u.restore(),translate:(e,t)=>u.translate(~~e,~~t),scale:(e,t)=>u.scale(e,t||e),rotate:e=>u.rotate(e),alpha(e){u.globalAlpha=T.clamp(e,0,1)},path:e=>new Path2D(e),fill(e,t){u.fillStyle=E[~~e%E.length],t?u.fill(t):u.fill()},stroke(e,t){u.strokeStyle=E[~~e%E.length],t?u.stroke(t):u.stroke()},clip(e){u.clip(e)},sfx:(e,t=0,l=1)=>!(a.zzfxV<=0)&&(!navigator.userActivation||!!navigator.userActivation.hasBeenActive)&&(e=e||z,(0!==t||1!==l)&&((e=e.slice())[0]=l*(e[0]||1),e[10]=~~e[10]+t),s.apply(0,e),e),volume(e){a.zzfxV=e},canvas:()=>d,use(e,t={}){f?L(e,t):c.push([e,t])},listen:(e,t)=>(C[e=e.toLowerCase()]=C[e]||new Set,C[e].add(t),()=>C[e].delete(t)),emit(e,t,a,l,n){f&&(A("before:"+(e=e.toLowerCase()),t,a,l,n),A(e,t,a,l,n),A("after:"+e,t,a,l,n))},pal(t=e){E=t},def(e,l){T[e]=l,t.global&&(a[e]=l)},timescale(e){g=e},framerate(e){w=1/~~e},stat(e){let l={index:e,value:[t,f,x,p,C,E,z,g,a.zzfxV||1,k,y,b][e]};return T.emit("stat",l),l.value},quit(){for(let e of(cancelAnimationFrame(x),x=0,T.emit("quit"),o))e();if(C={},t.global){for(let e in T)delete a[e];delete a.ENGINE}}};for(let e of"PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp".split(","))T[e]=l[e];function I(){let e=t.loop?t.loop:a;for(let t in C)e[t]&&T.listen(t,e[t]);for(let[e,t]of c)L(e,t);if(t.autoscale&&r(a,"resize",S),t.tapEvents){let e=(e,t)=>[(e-d.offsetLeft)/p,(t-d.offsetTop)/p],t=new Map,l=(e,a,l)=>{let n={x:a,y:l,startX:a,startY:l,ts:performance.now()};return t.set(e,n),n},n=(e,a,n)=>{let i=t.get(e)||l(e);i.x=a,i.y=n},i=e=>e&&performance.now()-e.ts<=300,o=e=>e.preventDefault(),s=!1;r(d,"mousedown",t=>{if(0===t.button){o(t);let[a,n]=e(t.pageX,t.pageY);T.emit("tap",a,n,0),l(0,a,n),s=!0}}),r(d,"mouseup",a=>{if(0===a.button){o(a);let l=t.get(0),[n,r]=e(a.pageX,a.pageY);i(l)&&T.emit("tapped",l.startX,l.startY,0),T.emit("untap",n,r,0),t.delete(0),s=!1}}),r(d,"mousemove",t=>{o(t);let[a,l]=e(t.pageX,t.pageY);T.def("MX",a),T.def("MY",l),s&&(T.emit("tapping",a,l,0),n(0,a,l))}),r(d,"touchstart",t=>{for(let a of(o(t),t.changedTouches)){let[t,n]=e(a.pageX,a.pageY);T.emit("tap",t,n,a.identifier+1),l(a.identifier+1,t,n)}}),r(d,"touchmove",t=>{for(let a of(o(t),t.changedTouches)){let[t,l]=e(a.pageX,a.pageY);T.emit("tapping",t,l,a.identifier+1),n(a.identifier+1,t,l)}});let f=e=>{o(e);let a=[];if(e.targetTouches.length>0)for(let t of e.targetTouches)a.push(t.identifier+1);for(let[e,l]of t)a.includes(e)||(i(l)&&T.emit("tapped",l.startX,l.startY,e),T.emit("untap",l.x,l.y,e),t.delete(e))};r(d,"touchend",f),r(d,"touchcancel",f),r(a,"blur",()=>{for(let[e,a]of(s=!1,t))T.emit("untap",a.x,a.y,e),t.delete(e)})}if(t.keyboardEvents){let e=new Set,t=new Set,l=(e,t="")=>(t=t.toLowerCase())?e.has("space"===t?" ":t):e.size>0;r(a,"keydown",a=>{let l=a.key.toLowerCase();e.has(l)||(e.add(l),t.add(l))}),r(a,"keyup",t=>{e.delete(t.key.toLowerCase())}),r(a,"blur",()=>e.clear()),T.listen("after:update",()=>t.clear()),T.def("iskeydown",t=>l(e,t)),T.def("iskeypressed",e=>l(t,e))}f=!0,T.emit("init",T),m=performance.now(),x=i(P)}function P(e){let a=0;if(t.animate){for(v+=l.min(.1,(e-m)/1e3),m=e;v>=w;)a++,T.emit("update",w*g,a),T.def("T",T.T+w*g),v-=w;x&&(x=i(P))}else a=1;a&&(T.textalign("start","top"),T.emit("draw"))}function S(){let e=t.width||a.innerWidth,n=t.height||t.width||a.innerHeight;T.def("W",d.width=e),T.def("H",d.height=n),T.def("CX",T.W/2),T.def("CY",T.H/2),t.autoscale&&(d.style.display||(d.style.display="block",d.style.margin="auto"),p=l.min(a.innerWidth/T.W,a.innerHeight/T.H),p=(t.pixelart?~~p:p)||1,d.style.width=T.W*p+"px",d.style.height=T.H*p+"px"),(!t.antialias||t.pixelart)&&(u.imageSmoothingEnabled=!1,d.style.imageRendering="pixelated"),T.emit("resized",p),T.cls(0),t.animate||i(P)}function A(e,t,a,l,n){if(C[e])for(let i of C[e])i(t,a,l,n)}function L(e,t){let a=e(T,t);for(let e in a)T.def(e,a[e])}if(t.global){if(a.ENGINE)throw Error("two global litecanvas detected");Object.assign(a,T),a.ENGINE=T}return t.canvas&&(d=document.querySelector(t.canvas)),d=d||document.createElement("canvas"),T.def("CANVAS",d),u=d.getContext("2d"),r(d,"click",()=>a.focus()),d.style="",S(),d.parentNode||document.body.appendChild(d),"loading"===document.readyState?r(a,"DOMContentLoaded",()=>i(I)):i(I),T}})();
1
+ (()=>{var e=["#111","#6a7799","#aec2c2","#FFF1E8","#e83b3b","#fabc20","#155fd9","#3cbcfc","#327345","#63c64d","#6c2c1f","#ac7c00"];globalThis.litecanvas=function(t={}){let a=window,n=Math,l=2*n.PI,i=requestAnimationFrame,r=[],o=(e,t,a)=>{e.addEventListener(t,a,!1),r.push(()=>e.removeEventListener(t,a,!1))},s=(e=>{let t=new AudioContext;return e.zzfxV=1,(a=1,n=.05,l=220,i=0,r=0,o=.1,s=0,f=1,c=0,d=0,p=0,u=0,h=0,g=0,m=0,w=0,v=0,x=1,b=0,y=0,k=0)=>{let E=Math,z=2*E.PI,T=c*=500*z/44100/44100,C=l*=(1-n+2*n*E.random(n=[]))*z/44100,I=0,P=0,A=0,L=1,S=0,X=0,Y=0,M=k<0?-1:1,D=z*M*k*2/44100,H=E.cos(D),N=E.sin,W=N(D)/4,F=1+W,q=-2*H/F,B=(1-W)/F,O=(1+M*H)/2/F,V=-(M+H)/F,R=0,G=0,$=0,j=0;for(i=44100*i+9,b*=44100,r*=44100,o*=44100,v*=44100,d*=500*z/85766121e6,m*=z/44100,p*=z/44100,u*=44100,h=44100*h|0,a*=.3*e.zzfxV,M=i+b+r+o+v|0;A<M;n[A++]=Y*a)++X%(100*w|0)||(Y=s?1<s?2<s?3<s?N(I*I):E.max(E.min(E.tan(I),1),-1):1-(2*I/z%2+2)%2:1-4*E.abs(E.round(I/z)-I/z):N(I),Y=(h?1-y+y*N(z*A/h):1)*(Y<0?-1:1)*E.abs(Y)**f*(A<i?A/i:A<i+b?1-(A-i)/b*(1-x):A<i+b+r?x:A<M-v?(M-A-v)/o*x:0),Y=v?Y/2+(v>A?0:(A<M-v?1:(M-A)/v)*n[A-v|0]/2/a):Y,k&&(Y=j=O*R+V*(R=G)+O*(G=Y)-B*$-q*($=j))),I+=(D=(l+=c+=d)*E.cos(m*P++))+D*g*N(A**5),L&&++L>u&&(l+=p,C+=p,L=0),!h||++S%h||(l=C,c=T,L=L||1);(a=t.createBuffer(1,M,44100)).getChannelData(0).set(n),(l=t.createBufferSource()).buffer=a,l.connect(t.destination),l.start()}})(a);t=Object.assign({width:null,height:null,autoscale:!0,pixelart:!1,antialias:!1,canvas:null,global:!0,loop:null,tapEvents:!0,keyboardEvents:!0,animate:!0},t);let f=!1,c=[],d,p=1,u,h=.5,g=1,m,w=1/60,v=0,x,b="sans-serif",y=20,k=Date.now(),E=e,z=[.5,0,1750,,,.3,1,,,,600,.1],T={init:null,update:null,draw:null,resized:null,tap:null,untap:null,tapping:null,tapped:null},C={W:0,H:0,T:0,MX:-1,MY:-1,TWO_PI:l,HALF_PI:l/4,lerp:(e,t,a)=>a*(t-e)+e,deg2rad:e=>n.PI/180*e,rad2deg:e=>180/n.PI*e,round:(e,t=0)=>{if(!t)return n.round(e);let a=10**t;return n.round(e*a)/a},clamp:(e,t,a)=>e<t?t:e>a?a:e,wrap:(e,t,a)=>e-(a-t)*n.floor((e-t)/(a-t)),map(e,t,a,n,l,i){let r=(e-t)/(a-t)*(l-n)+n;return i?C.clamp(r,n,l):r},norm:(e,t,a)=>C.map(e,t,a,0,1),wave:(e,t,a,n=Math.sin)=>e+(n(a)+1)/2*(t-e),rand:(e=0,t=1)=>(k=(1664525*k+0x3c6ef35f)%0x100000000)/0x100000000*(t-e)+e,randi:(e=0,t=1)=>n.floor(C.rand(e,t+1)),rseed(e){k=~~e},cls(e){null==e?u.clearRect(0,0,u.canvas.width,u.canvas.height):C.rectfill(0,0,u.canvas.width,u.canvas.height,e)},rect(e,t,a,n,l,i){u.beginPath(),u[i?"roundRect":"rect"](~~e-h,~~t-h,~~a+2*h,~~n+2*h,i),C.stroke(l)},rectfill(e,t,a,n,l,i){u.beginPath(),u[i?"roundRect":"rect"](~~e,~~t,~~a,~~n,i),C.fill(l)},circ(e,t,a,n){u.beginPath(),u.arc(~~e,~~t,~~a,0,l),C.stroke(n)},circfill(e,t,a,n){u.beginPath(),u.arc(~~e,~~t,~~a,0,l),C.fill(n)},line(e,t,a,n,l){u.beginPath();let i=.5*(0!==h&&~~e==~~a),r=.5*(0!==h&&~~t==~~n);u.moveTo(~~e+i,~~t+r),u.lineTo(~~a+i,~~n+r),C.stroke(l)},linewidth(e){u.lineWidth=~~e,h=.5*(0!=~~e%2)},linedash(e,t=0){u.setLineDash(e),u.lineDashOffset=t},text(e,t,a,n=3,l="normal"){u.font=`${l} ${y}px ${b}`,u.fillStyle=E[~~n%E.length],u.fillText(a,~~e,~~t)},textfont(e){b=e},textsize(e){y=e},textalign(e,t){e&&(u.textAlign=e),t&&(u.textBaseline=t)},image(e,t,a){u.drawImage(a,~~e,~~t)},paint(e,t,a,n={}){let l=n.canvas||new OffscreenCanvas(1,1),i=n.scale||1,r=u;if(l.width=e*i,l.height=t*i,(u=l.getContext("2d")).scale(i,i),Array.isArray(a)){let e=0,t=0;for(let n of(u.imageSmoothingEnabled=!1,a)){for(let a of n)" "!==a&&"."!==a&&C.rectfill(e,t,1,1,parseInt(a,16)),e++;t++,e=0}}else a(u);return u=r,l.transferToImageBitmap()},ctx:e=>(e&&(u=e),u),push:()=>u.save(),pop:()=>u.restore(),translate:(e,t)=>u.translate(~~e,~~t),scale:(e,t)=>u.scale(e,t||e),rotate:e=>u.rotate(e),alpha(e){u.globalAlpha=C.clamp(e,0,1)},path:e=>new Path2D(e),fill(e,t){u.fillStyle=E[~~e%E.length],t?u.fill(t):u.fill()},stroke(e,t){u.strokeStyle=E[~~e%E.length],t?u.stroke(t):u.stroke()},clip(e){u.clip(e)},sfx:(e,t=0,n=1)=>!(a.zzfxV<=0)&&(!navigator.userActivation||!!navigator.userActivation.hasBeenActive)&&(e=e||z,(0!==t||1!==n)&&((e=e.slice())[0]=n*(e[0]||1),e[10]=~~e[10]+t),s.apply(0,e),e),volume(e){a.zzfxV=e},canvas:()=>d,use(e,t={}){f?S(e,t):c.push([e,t])},listen:(e,t)=>(T[e=e.toLowerCase()]=T[e]||new Set,T[e].add(t),()=>T[e].delete(t)),emit(e,t,a,n,l){f&&(L("before:"+(e=e.toLowerCase()),t,a,n,l),L(e,t,a,n,l),L("after:"+e,t,a,n,l))},pal(t=e){E=t},def(e,n){C[e]=n,t.global&&(a[e]=n)},timescale(e){g=e},framerate(e){w=1/~~e},stat(e){let n={index:e,value:[t,f,x,p,T,E,z,g,a.zzfxV||1,k,y,b][e]};return C.emit("stat",n),n.value},quit(){for(let e of(cancelAnimationFrame(x),x=0,C.emit("quit"),r))e();if(T={},t.global){for(let e in C)delete a[e];delete a.ENGINE}f=!1}};for(let e of"PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp".split(","))C[e]=n[e];function I(){let e=t.loop?t.loop:a;for(let t in T)e[t]&&C.listen(t,e[t]);for(let[e,t]of c)S(e,t);if(t.autoscale&&o(a,"resize",A),t.tapEvents){let e=(e,t)=>[(e-d.offsetLeft)/p,(t-d.offsetTop)/p],t=new Map,n=(e,a,n)=>{let l={x:a,y:n,startX:a,startY:n,ts:performance.now()};return t.set(e,l),l},l=(e,a,l)=>{let i=t.get(e)||n(e);i.x=a,i.y=l},i=e=>e&&performance.now()-e.ts<=300,r=e=>e.preventDefault(),s=!1;o(d,"mousedown",t=>{if(0===t.button){r(t);let[a,l]=e(t.pageX,t.pageY);C.emit("tap",a,l,0),n(0,a,l),s=!0}}),o(d,"mouseup",a=>{if(0===a.button){r(a);let n=t.get(0),[l,o]=e(a.pageX,a.pageY);i(n)&&C.emit("tapped",n.startX,n.startY,0),C.emit("untap",l,o,0),t.delete(0),s=!1}}),o(d,"mousemove",t=>{r(t);let[a,n]=e(t.pageX,t.pageY);C.def("MX",a),C.def("MY",n),s&&(C.emit("tapping",a,n,0),l(0,a,n))}),o(d,"touchstart",t=>{for(let a of(r(t),t.changedTouches)){let[t,l]=e(a.pageX,a.pageY);C.emit("tap",t,l,a.identifier+1),n(a.identifier+1,t,l)}}),o(d,"touchmove",t=>{for(let a of(r(t),t.changedTouches)){let[t,n]=e(a.pageX,a.pageY);C.emit("tapping",t,n,a.identifier+1),l(a.identifier+1,t,n)}});let f=e=>{r(e);let a=[];if(e.targetTouches.length>0)for(let t of e.targetTouches)a.push(t.identifier+1);for(let[e,n]of t)a.includes(e)||(i(n)&&C.emit("tapped",n.startX,n.startY,e),C.emit("untap",n.x,n.y,e),t.delete(e))};o(d,"touchend",f),o(d,"touchcancel",f),o(a,"blur",()=>{for(let[e,a]of(s=!1,t))C.emit("untap",a.x,a.y,e),t.delete(e)})}if(t.keyboardEvents){let e=new Set,t=new Set,n=(e,t="")=>(t=t.toLowerCase())?e.has("space"===t?" ":t):e.size>0;o(a,"keydown",a=>{let n=a.key.toLowerCase();e.has(n)||(e.add(n),t.add(n))}),o(a,"keyup",t=>{e.delete(t.key.toLowerCase())}),o(a,"blur",()=>e.clear()),C.listen("after:update",()=>t.clear()),C.def("iskeydown",t=>n(e,t)),C.def("iskeypressed",e=>n(t,e))}f=!0,C.emit("init",C),m=performance.now(),x=i(P)}function P(e){let a=0;if(t.animate){for(v+=n.min(.1,(e-m)/1e3),m=e;v>=w;)a++,C.emit("update",w*g,a),C.def("T",C.T+w*g),v-=w;x&&(x=i(P))}else a=1;a&&(C.textalign("start","top"),C.emit("draw"))}function A(){let e=t.width||a.innerWidth,l=t.height||t.width||a.innerHeight;C.def("W",d.width=e),C.def("H",d.height=l),t.autoscale&&(d.style.display||(d.style.display="block",d.style.margin="auto"),p=n.min(a.innerWidth/C.W,a.innerHeight/C.H),p=(t.pixelart?~~p:p)||1,d.style.width=C.W*p+"px",d.style.height=C.H*p+"px"),(!t.antialias||t.pixelart)&&(u.imageSmoothingEnabled=!1,d.style.imageRendering="pixelated"),C.emit("resized",p),C.cls(0),t.animate||i(P)}function L(e,t,a,n,l){if(T[e])for(let i of T[e])i(t,a,n,l)}function S(e,t){let a=e(C,t);for(let e in a)C.def(e,a[e])}if(t.global){if(a.ENGINE)throw Error("two global litecanvas detected");Object.assign(a,C),a.ENGINE=C}return t.canvas&&(d=document.querySelector(t.canvas)),u=(d=d||document.createElement("canvas")).getContext("2d"),o(d,"click",()=>a.focus()),d.style="",A(),d.parentNode||document.body.appendChild(d),"loading"===document.readyState?o(a,"DOMContentLoaded",()=>i(I)):i(I),C}})();
package/package.json CHANGED
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "name": "litecanvas",
3
- "version": "0.84.0",
3
+ "version": "0.84.2",
4
4
  "description": "Lightweight HTML5 canvas 2D game engine suitable for small projects and creative coding. Inspired by PICO-8 and P5/Processing.",
5
5
  "license": "MIT",
6
6
  "author": "Luiz Bills <luizbills@pm.me>",
7
7
  "contributors": [],
8
8
  "devDependencies": {
9
- "@swc/core": "^1.12.5",
9
+ "@litecanvas/jsdom-extras": "^1.1.0",
10
+ "@swc/core": "^1.12.6",
11
+ "@types/jsdom": "^21.1.7",
10
12
  "ava": "^6.4.0",
11
13
  "esbuild": "^0.25.5",
12
14
  "gzip-size": "^7.0.0",
13
- "prettier": "^3.5.3",
14
- "tap-min": "^3.0.0",
15
- "@types/jsdom": "^21.1.7",
16
- "jsdom": "^26.1.0"
15
+ "jsdom": "^26.1.0",
16
+ "prettier": "^3.6.0",
17
+ "tap-min": "^3.0.0"
17
18
  },
18
19
  "homepage": "https://litecanvas.github.io/about.html",
19
20
  "repository": {
@@ -56,8 +57,10 @@
56
57
  ],
57
58
  "ava": {
58
59
  "files": [
59
- "tests/**/*",
60
- "!tests/_mocks"
60
+ "tests/**/*.js"
61
61
  ]
62
- }
62
+ },
63
+ "trustedDependencies": [
64
+ "@swc/core"
65
+ ]
63
66
  }
package/src/index.js CHANGED
@@ -10,20 +10,20 @@ import { assert } from './dev.js'
10
10
  * @returns {LitecanvasInstance}
11
11
  */
12
12
  export default function litecanvas(settings = {}) {
13
- const /** @type {typeof globalThis} */
14
- root = globalThis,
13
+ const /** @type {Window} */
14
+ root = window,
15
15
  math = Math,
16
16
  TWO_PI = math.PI * 2,
17
17
  raf = requestAnimationFrame,
18
18
  /** @type {Function[]} */
19
19
  _browserEventListeners = [],
20
- /** @type {(elem:HTMLElement, evt:string, callback:(event:Event)=>void)=>void} */
20
+ /** @type {(elem: EventTarget, evt: string, callback: (event: Event) => void) => void} */
21
21
  on = (elem, evt, callback) => {
22
22
  elem.addEventListener(evt, callback, false)
23
23
  _browserEventListeners.push(() => elem.removeEventListener(evt, callback, false))
24
24
  },
25
- zzfx = setupZzFX(root),
26
25
  isNumber = Number.isFinite,
26
+ zzfx = setupZzFX(root),
27
27
  /** @type {LitecanvasOptions} */
28
28
  defaults = {
29
29
  width: null,
@@ -649,8 +649,11 @@ export default function litecanvas(settings = {}) {
649
649
  * @see https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
650
650
  */
651
651
  paint(width, height, drawing, options = {}) {
652
- DEV: assert(isNumber(width), 'paint: 1st param must be a number')
653
- DEV: assert(isNumber(height), 'paint: 2nd param must be a number')
652
+ DEV: assert(isNumber(width) && width >= 1, 'paint: 1st param must be a positive number')
653
+ DEV: assert(
654
+ isNumber(height) && height >= 1,
655
+ 'paint: 2nd param must be a positive number'
656
+ )
654
657
  DEV: assert(
655
658
  'function' === typeof drawing || Array.isArray(drawing),
656
659
  'paint: 3rd param must be a function or array'
@@ -659,6 +662,10 @@ export default function litecanvas(settings = {}) {
659
662
  (options && !options.scale) || isNumber(options.scale),
660
663
  'paint: 4th param (options.scale) must be a number'
661
664
  )
665
+ DEV: assert(
666
+ (options && !options.canvas) || options.canvas instanceof OffscreenCanvas,
667
+ 'paint: 4th param (options.canvas) must be an OffscreenCanvas'
668
+ )
662
669
 
663
670
  const /** @type {OffscreenCanvas} */
664
671
  canvas = options.canvas || new OffscreenCanvas(1, 1),
@@ -667,19 +674,17 @@ export default function litecanvas(settings = {}) {
667
674
 
668
675
  canvas.width = width * scale
669
676
  canvas.height = height * scale
670
- _ctx = canvas.getContext('2d')
671
677
 
678
+ _ctx = canvas.getContext('2d')
672
679
  _ctx.scale(scale, scale)
673
680
 
674
681
  // draw pixel art if `draw` is a array
675
- // @ts-ignore
676
- if (drawing.push) {
682
+ if (Array.isArray(drawing)) {
677
683
  let x = 0,
678
684
  y = 0
679
685
 
680
686
  _ctx.imageSmoothingEnabled = false
681
687
 
682
- // @ts-ignore
683
688
  for (const str of drawing) {
684
689
  for (const color of str) {
685
690
  if (' ' !== color && '.' !== color) {
@@ -692,7 +697,6 @@ export default function litecanvas(settings = {}) {
692
697
  x = 0
693
698
  }
694
699
  } else {
695
- // @ts-ignore
696
700
  drawing(_ctx)
697
701
  }
698
702
 
@@ -880,6 +884,7 @@ export default function litecanvas(settings = {}) {
880
884
  DEV: assert(isNumber(volumeFactor), 'sfx: 3rd param must be a number')
881
885
 
882
886
  if (
887
+ // @ts-ignore
883
888
  root.zzfxV <= 0 ||
884
889
  (navigator.userActivation && !navigator.userActivation.hasBeenActive)
885
890
  ) {
@@ -909,6 +914,7 @@ export default function litecanvas(settings = {}) {
909
914
  volume(value) {
910
915
  DEV: assert(isNumber(value), 'volume: 1st param must be a number')
911
916
 
917
+ // @ts-ignore
912
918
  root.zzfxV = value
913
919
  },
914
920
 
@@ -918,9 +924,7 @@ export default function litecanvas(settings = {}) {
918
924
  *
919
925
  * @returns {HTMLCanvasElement}
920
926
  */
921
- canvas() {
922
- return _canvas
923
- },
927
+ canvas: () => _canvas,
924
928
  /**
925
929
  * Prepares a plugin to be loaded
926
930
  *
@@ -1047,7 +1051,7 @@ export default function litecanvas(settings = {}) {
1047
1051
  * @returns {any}
1048
1052
  */
1049
1053
  stat(n) {
1050
- DEV: assert(isNumber(n) && n >= 0, 'stat: 1st param must be a positive number')
1054
+ DEV: assert(isNumber(n) && n >= 0, 'stat: 1st param must be a number')
1051
1055
 
1052
1056
  const list = [
1053
1057
  // 0
@@ -1067,6 +1071,7 @@ export default function litecanvas(settings = {}) {
1067
1071
  // 7
1068
1072
  _timeScale,
1069
1073
  // 8
1074
+ // @ts-ignore
1070
1075
  root.zzfxV || 1,
1071
1076
  // 9
1072
1077
  _rngSeed,
@@ -1090,6 +1095,7 @@ export default function litecanvas(settings = {}) {
1090
1095
  quit() {
1091
1096
  // stop the game loop (update & draw)
1092
1097
  cancelAnimationFrame(_rafid)
1098
+
1093
1099
  _rafid = 0
1094
1100
 
1095
1101
  // emit "quit" event to manual clean ups
@@ -1108,8 +1114,12 @@ export default function litecanvas(settings = {}) {
1108
1114
  for (const key in instance) {
1109
1115
  delete root[key]
1110
1116
  }
1117
+ // @ts-ignore
1111
1118
  delete root.ENGINE
1112
1119
  }
1120
+
1121
+ // unset that flag
1122
+ _initialized = false
1113
1123
  },
1114
1124
  }
1115
1125
 
@@ -1133,7 +1143,6 @@ export default function litecanvas(settings = {}) {
1133
1143
 
1134
1144
  // listen window resize event when "autoscale" is enabled
1135
1145
  if (settings.autoscale) {
1136
- // @ts-ignore
1137
1146
  on(root, 'resize', resizeCanvas)
1138
1147
  }
1139
1148
 
@@ -1306,7 +1315,6 @@ export default function litecanvas(settings = {}) {
1306
1315
  on(_canvas, 'touchend', _touchEndHandler)
1307
1316
  on(_canvas, 'touchcancel', _touchEndHandler)
1308
1317
 
1309
- // @ts-ignore
1310
1318
  on(root, 'blur', () => {
1311
1319
  _pressingMouse = false
1312
1320
  for (const [id, tap] of _taps) {
@@ -1333,7 +1341,6 @@ export default function litecanvas(settings = {}) {
1333
1341
  return !key ? keySet.size > 0 : keySet.has('space' === key ? ' ' : key)
1334
1342
  }
1335
1343
 
1336
- // @ts-ignore
1337
1344
  on(root, 'keydown', (/** @type {KeyboardEvent} */ event) => {
1338
1345
  const key = event.key.toLowerCase()
1339
1346
  if (!_keysDown.has(key)) {
@@ -1342,12 +1349,10 @@ export default function litecanvas(settings = {}) {
1342
1349
  }
1343
1350
  })
1344
1351
 
1345
- // @ts-ignore
1346
1352
  on(root, 'keyup', (/** @type {KeyboardEvent} */ event) => {
1347
1353
  _keysDown.delete(event.key.toLowerCase())
1348
1354
  })
1349
1355
 
1350
- // @ts-ignore
1351
1356
  on(root, 'blur', () => _keysDown.clear())
1352
1357
  instance.listen('after:update', () => _keysPress.clear())
1353
1358
 
@@ -1448,7 +1453,6 @@ export default function litecanvas(settings = {}) {
1448
1453
 
1449
1454
  DEV: assert(_canvas && _canvas.tagName === 'CANVAS', 'Invalid canvas element')
1450
1455
 
1451
- instance.def('CANVAS', _canvas)
1452
1456
  _ctx = _canvas.getContext('2d')
1453
1457
 
1454
1458
  on(_canvas, 'click', () => root.focus())
@@ -1483,9 +1487,6 @@ export default function litecanvas(settings = {}) {
1483
1487
  instance.def('W', (_canvas.width = width))
1484
1488
  instance.def('H', (_canvas.height = height))
1485
1489
 
1486
- instance.def('CX', instance.W / 2)
1487
- instance.def('CY', instance.H / 2)
1488
-
1489
1490
  if (settings.autoscale) {
1490
1491
  if (!_canvas.style.display) {
1491
1492
  _canvas.style.display = 'block'
@@ -1525,7 +1526,6 @@ export default function litecanvas(settings = {}) {
1525
1526
  */
1526
1527
  function triggerEvent(eventName, arg1, arg2, arg3, arg4) {
1527
1528
  if (!_events[eventName]) return
1528
- // @ts-ignore
1529
1529
  for (const callback of _events[eventName]) {
1530
1530
  callback(arg1, arg2, arg3, arg4)
1531
1531
  }
@@ -1550,17 +1550,18 @@ export default function litecanvas(settings = {}) {
1550
1550
  }
1551
1551
 
1552
1552
  if (settings.global) {
1553
+ // @ts-ignore
1553
1554
  if (root.ENGINE) {
1554
1555
  throw new Error('two global litecanvas detected')
1555
1556
  }
1556
1557
  Object.assign(root, instance)
1558
+ // @ts-ignore
1557
1559
  root.ENGINE = instance
1558
1560
  }
1559
1561
 
1560
1562
  setupCanvas()
1561
1563
 
1562
1564
  if ('loading' === document.readyState) {
1563
- // @ts-ignore
1564
1565
  on(root, 'DOMContentLoaded', () => raf(init))
1565
1566
  } else {
1566
1567
  raf(init)
package/src/zzfx.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.0 by Frank Force | https://github.com/KilledByAPixel/ZzFX
2
2
  // prettier-ignore
3
3
  /**
4
- * @param {typeof globalThis} global
4
+ * @param {Window} global
5
5
  * @returns {Function} the `zzfx()` function
6
6
  */
7
7
  export const setupZzFX = (global) => {
package/types/index.d.ts CHANGED
@@ -515,7 +515,7 @@ declare global {
515
515
  * @param callback the function that is called when the event occurs
516
516
  * @returns a function to remove the listener
517
517
  */
518
- function listen(event: string, callback: Function): Function | null
518
+ function listen(event: string, callback: Function): Function
519
519
  /**
520
520
  * Call all listeners attached to a game event
521
521
  *
package/types/types.d.ts CHANGED
@@ -502,7 +502,7 @@ type LitecanvasInstance = {
502
502
  * @param callback the function that is called when the event occurs
503
503
  * @returns a function to remove the listener
504
504
  */
505
- listen(event: string, callback: Function): Function | null
505
+ listen(event: string, callback: Function): Function
506
506
  /**
507
507
  * Call all listeners attached to a game event
508
508
  *
@@ -645,6 +645,6 @@ type LitecanvasGameLoop = {
645
645
  tapping?: (tapX: number, tapY: number, tapId: number) => void
646
646
  }
647
647
 
648
- type drawCallback = (context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D) => void
648
+ type drawCallback = (context: OffscreenCanvasRenderingContext2D) => void
649
649
 
650
650
  type pluginCallback = (instance: LitecanvasInstance, config?: any) => any