litecanvas 0.67.2 → 0.68.0

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Luiz Bills
3
+ Copyright (c) 2025 Luiz Bills
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -10,12 +10,12 @@ Litecanvas is a lightweight HTML5 canvas engine suitable for small web games, pr
10
10
 
11
11
  ### Features
12
12
 
13
- - **Tiny**: Only `~4KB` (minified + gzipped).
14
- - **Simple API**: Just few functions to draw shapes and some utilities.
15
- - **Predefined colors**: Just use a number (from 0 to 11) to choose a color in our 12-color palette.
16
- - **ZzFX**: Play or create sound effects with [ZzFX](https://killedbyapixel.github.io/ZzFX/).
17
- - **Extensible**: Use or create [plugins](https://www.npmjs.com/search?q=keywords:litecanvas) to add functionalities or change the engine.
18
- - **Playground**: Access or install the [playground](https://litecanvas.js.org/) webapp to code and share games (even offline).
13
+ - **Tiny**: Only `~4KB` (minified + gzipped).
14
+ - **Simple API**: Just few functions to draw shapes and some utilities.
15
+ - **Predefined colors**: Just use a number (from 0 to 11) to choose a color in our 12-color palette.
16
+ - **ZzFX**: Play or create sound effects with [ZzFX](https://killedbyapixel.github.io/ZzFX/).
17
+ - **Extensible**: Use or create [plugins](https://www.npmjs.com/search?q=keywords:litecanvas) to add functionalities or change the engine.
18
+ - **Playground**: Access or install the [playground](https://litecanvas.js.org/) webapp to code and share games (even offline).
19
19
 
20
20
  [Learn more in the cheatsheet...](https://litecanvas.js.org/about.html)
21
21
 
@@ -38,12 +38,12 @@ npm install litecanvas
38
38
  ```
39
39
 
40
40
  ```js
41
- // import the engine or put the script in your HTML
41
+ // import the package or put the CDN script in your HTML
42
42
  // CDN: https://unpkg.com/litecanvas/dist/dist.min.js
43
43
  import litecanvas from 'litecanvas'
44
44
 
45
- // you can setup other configurations here
46
- // learn more in the cheatsheet
45
+ // Start and setup the engine
46
+ // learn more: https://litecanvas.js.org/about.html#settings
47
47
  litecanvas({
48
48
  loop: { init, update, draw, tapped },
49
49
  })
@@ -86,18 +86,19 @@ Check out our [Cheatsheet](https://litecanvas.js.org/about.html).
86
86
 
87
87
  Try some demos in our playground:
88
88
 
89
- - [Pong](https://litecanvas.js.org?c=eJy1Vlly20YQ%2FccpOl8ATBACuEiWYslFM5DlKkdMkUwkJpVKwcCQnAoEoIChpdiWr%2BAT%2BC%2BHyHlygVwh3TODTZYU%2F0QsDdELenm9DBMmIA%2FjCzgGf%2BA5BuAf0mdEj2vykkivEa%2BQHg01HbNSkALpKc6bMElaj6vmccHfsZZpYk3STUK8yhovpLeaWjVUmTMWIzmoQimjrGi9m%2FA1K5EcKlKwG0EOta4ICyFfX4dJyQwj4YJFYfo2LK33UuOax2J7BMMKhy3jm604gtFTZNzahrG3B9NdKbIr%2BPnd6SWU2S6NS1hnBYgtL2ETXjHSyQsmBGdFn29SjM%2BIsrQU8GL24%2Fk0QPe%2FOOCAz4b05XpDOgbIcGBAHAcOR54WVB8XJe6%2B0t%2F%2F9duHfSyWk%2FmSXHju2EFUYIxIyddGeAzbXsYHnvYsD%2B3IGz1m%2FsXk9evfZqeni%2Bk8CM7Jj%2B8OfZnOGB14rkdeyQ8eB9KbpPqH7iGe%2B3TSx3N9v3oayPfkOZKnTxka610aCZ6lwFMuLBveN42FfqfB%2BTKYXzYdVvNW0Iez4NXLsyXswajTBqhz8eq75RkK%2FIFx2%2FIhwjxnsXXjwB%2BVJ74G6xvdMhWv20Wi2LGGvb6xJPh2zUIEd0UqydvOnHz8aN1glHLq9mBgd0LZ5XEomBWL%2BwNRRgErJLjAsSmjgrH0ea2pJ%2BAYR6KtTJ0J2VtWPDekKrKukMS2ZRRHnLD2pCesyC05zSpmVVm7fjXOUlOglmi9j2SoDaqY6pCk0WcUjzbvSRHDGWzkPQXHiapQrarqpbAyaouq4r1moZzokrcLVbWKv%2B91eNQqBw2rWhnquw9%2Bp6Tdhrd1MSscVK2kVcizklMBW23aO1bL7IneW08gFq2OVeJVV6zWGCL7BndL1N5W2qfiK5%2FYLnew1o7byCgIP3zQQT2rZW2wVJzH0G%2FSbwKoG%2F22KZpK4UFjcmc%2FaqpOKNqy6HeVT5QlCS9pCq652LYbk1yitGCRUDk6%2BmKpA2g%2Fqdala0qeF7iI7HsCbCWr7pEeXXJfEbUEQElt1SZytXcHOS7C63pvRUlpefaji4W2VJjgurXMiKWCFaYD5hUnCEy7o1Vikla11LoiSy9Gp9qGaGM5%2BQGWM3U1oM2hfbeWqvVPaEKbcAjqNU8S60sw6edBbUdmx4tI6j5Ummp3PJDBfRhIhAiCbZhueLq5g4Hl4%2B1F%2F%2Bbfn%2F%2F8569PYLoFy1koVDa2A6P77bI0fsRqtW8q65Zp4jTJ7rBdzHtBUVl4E3v2XST%2F70piVAO8Xc2Xk%2B8DmP0UzM1ODb4wh%2FvEc5%2BO%2F8tqT6G4mM7mwREl%2B1UpY6f%2FC73Is6Q%3D)
90
- - [Bouncing Ball](https://litecanvas.js.org?c=eJxtUstugzAQvPMVewTiBCdtpVYpPVStSu6ReraMiSy5gIyhRUn%2BvQZvg0NzsGTveGdmH0oYqKtGGlmVkEIneLimlNgTkQDsW1Vcmn64HzTr8KpZLtvG%2Fr9%2FDAIljeCs7FgTRkFQtCUfyWQpTRjB0WNBgc%2Fd2z6LNyR7331k%2B2QTbSd2%2FEIRtNDZ42zrnBkR5sbx%2Fhlf%2FcAivajYVwy58fH%2BCu8d7hlzBGhhyvcyPHhKTxJ4raw3AVVRgMgPorFRWUA4OVtgr15gLBtOJ8%2F2EsFnoK6iK0txCsv19kaUrp4exrDXgBS4Yl%2B1p0xwTMQpo1ZkE89zl7Y%2BuPjEzs%2F99KOf%2F9LDyFwKKowC%2FtRyzb5xE7hqQjrOm0vNC6mUb3jiJGj9brYAw27YwnrHpoVpdQnHIWIlfwGU6d0i)
91
- - [Scroller](https://litecanvas.js.org?c=eJxVUMFOwzAMvfcrzAEtacNIxwZDsAPSJoG0AxJIO0w7hDZdI6XN1HhQgfbvOOs22CGJ7ff8nh1rUGeq%2FlSe8SgqtnWGxtVgaoPAOPxEAJX2Xq01TKC30DZzlQZ0YE%2BNFz0itQQvXqbvzxRvlKVsORQjcSvuxFjci1SKNF1Fu38W202uUAPLsbNB6pnNn17fZlPKvPnWR8nrwwR9q%2Bs1loSWBI3k3vZqAjdSQgw5nsnnjfo6bpBZzySnoHANMKsRDAnIB3oe4VycaknSdQEEJoa5MGGyn8aGn8p0UDcEHdv99sNjw4yA9I%2BVOesCiX5kWVjnGoYYjy4pPdjx1Z6LusWwMQsXP5VYpVrWChhInpg4YLHsjwWUyUAm3tRBbcjjUhzGEZ1hENjRZ%2FwCV%2F6J0w%3D%3D)
92
- - [3D rojection](https://litecanvas.js.org?c=eJyNVU2P2jAQvfMrpodqnWI%2By6kt7WW3hVulrtTuRjk4JCyG4CDHFJYV%2F73jj5CYDUslROx579njGc844yqdMfGXFSRoteZbMVM8F8AFVySAlxZAxkW644lakGGA04IfUhjD7%2Bnt%2FQS%2BwuRu%2BmNyD9%2FKQQ9G8MmhOEbBRubL1K46hhANAOGAQh9%2FEbVTHA%2F8qbaYaaRXyLlQRaXuaB8o1D8RhV4Pwj2FZwqHyPKaaHWkAehcQ2oEf7GLwGt7pxGIqliliT6sNjDxlOlw91vHWna2m4SplCTKZsiS2sjq9gctNMxzCSRLFXAt%2FYyfLy6I3SwVT2qBpnbbigFmuSgU4jrChjVcMyX5nnS7XasKeRQYql5T5opZD5G23mbEGNCtP8Q4ElC9luVf5D6cuI5yhf%2FYzHeul1Eb3lbK6tqdaU5sPJU%2BcSVG%2BOjFOZFs56pglhWkH5gUYUSIvaLff%2F6iMAp0yDfbYkE0riQTRabTUxbBkFbFYUqoKT2j85QIdIpwCoRDGwYBvIcRrZwNfBpSECU1bmBNlwQUeAPhaIptg%2BeoR6EUMQoxdfeoag3EXREWhX39KxnaMPAM8TkjPjE%2B%2Bju6FOoNXfRNntksz%2FQlZahxF7nCZL4z2DkQO1HcIIqdKC4BRPgciN3o3XhsGQGoBX4B%2ByDcSZlLcjPFjpnxBGylpMWNuQR2VZkWZfk2Zdp46mUbBfYuhrZxnVRLq1qiyhwDh5XKVmOxXZv2YC0n5coqV3o%2Fq1zVlWB0bR1KHoWrCD5gfFZRuIwc4ei%2B1jW043ooaZXYUR9XpmorheZ42av6gWQJx2qw%2Bzr2xRdglhcngWmnojatE33El%2FVdK23y5%2BEtf86W8Xe58E6d%2B%2Bg784Ynj%2F%2FvyaU4XA%2FCq4e07onX592r6TsT7rEuw2f9d4i0%2BB%2FEGGLo)
93
- - [Rendering Benchmark](https://litecanvas.js.org?c=eJylVlGP4jYQfs%2BvsGilJLchS4DlWA5SnVatWmkrne7aJ4SESQxYaxJqm13SPf57x3ZIDGS5a%2BsH4szMN57xfDMhyTMhUZJnkudMoAlK82S3IZkME06wJD8zot48N6XPru8cDUOaZYT%2F%2BsfvjwCZO2MhC0ZiB4c4kfSZoFcHwVqCdfuF0NVajtAiZ%2BkH5%2BCMb0vjMbiMAcnzbBX%2F8unLSGn0CxqLLc4QTSet5Va0YlDAOzwUwuC0%2FyP44ybfZbLGGyVGa06Wk9ZPWKsnUafTQimWuF0KWkoSw8%2F4Fr%2BBubvAKEl8dw0DDhsOUic9vQ3qXoK0KO5eAfUvQVoU96%2BAhpcgLYqHV0DRoCGpgclqcAXXa8jLyOJemZmp6typiLfI0yLccrIlWeodCeef6TWHwiRnOQcGuj9EUeQ2mixw8rTicHL6UBkvl0vXUVwG6u84A1lGXtCfnx89lidY0jzzA52PkNACoDZ0VksWWzJCrthyKokbVPJEMxDdgLtQEMyT9SfM8UaEKwLNY1J3ffT1KwIu1LAXmsr1CGTdfi1clx3TH1qW0AhlL47qHv1rR3jxhTCSyJx7kNhWuGXoB8dhEGKCs2csPJNAeZrOKtQvxvZ4oFGYt8A5%2BI6z3GWJug9EMyo9v7wIY2fuAG5niylMiCrSqBs07qfVTi03hNWHpZ7WRdY6jN%2FQdTpK1%2Bk06rrKZ7dB9x5WD9YdrAvdYPC%2BF0W9u8Xi3%2Bmu%2BFSx3MNqikXlcD8c3jfnEIZK99a9KJ9nulm9fT0BiAQzoGuv1h%2F0zrfLSP9WRbRrarjhaKPbWwRMljRhBOlPArBBK0z7bEudKJvoI%2Be48Iwz3RO%2BZZxS1X%2FTKEDtaFZ%2BITjyGJGIgqLzAR5jZIFBcHPjW0kZP3sw5himg4%2Fe2WxGbSunM0xxiTFEvwaapvsApcVMRX1ysdoT5NFT3iCtqRJQrxOgyJ8F%2F8V0Vu2qK51SdfArghgKCEOHUhbw4FgVtGtQ7UsDIn%2FLJOHPmHmQ%2BSS27tKA67kSSrKXDzBvYQ%2Be4ItsjgoQDGy7jIxmT%2FY%2FhdMpNJ%2BeTPsfX616HlqzufFEl1B38GMXV72HCcNCPFIhQ5ymMDn1%2FwnXd2ryqiBymOwsX3nzz%2FCRIJxmK3R6EDJMFtAocOLBmmQpxy%2FVJAN2PzAY10iugd56VpozmPAg54sOwBndXGuBs4L8D45zu5jABKfS83CP2kDmMN1bsqKUFbWhumNlPEYd%2F2wwKDEEcyZL9%2BjdBHqznhaIMEFKR0Wzo6LJUXHu6DKqG3v%2BxHYbNwf7zT7%2FvhwajzWToDm3b8%2BK78h5g1dEZR0op8HJsD1S%2B%2BD8A9I%2FGIM%3D)
89
+ - [Pong](https://litecanvas.js.org?c=eJy1Vlly20YQ%2FccpOl8ATBACuEiWYslFM5DlKkdMkUwkJpVKwcCQnAoEoIChpdiWr%2BAT%2BC%2BHyHlygVwh3TODTZYU%2F0QsDdELenm9DBMmIA%2FjCzgGf%2BA5BuAf0mdEj2vykkivEa%2BQHg01HbNSkALpKc6bMElaj6vmccHfsZZpYk3STUK8yhovpLeaWjVUmTMWIzmoQimjrGi9m%2FA1K5EcKlKwG0EOta4ICyFfX4dJyQwj4YJFYfo2LK33UuOax2J7BMMKhy3jm604gtFTZNzahrG3B9NdKbIr%2BPnd6SWU2S6NS1hnBYgtL2ETXjHSyQsmBGdFn29SjM%2BIsrQU8GL24%2Fk0QPe%2FOOCAz4b05XpDOgbIcGBAHAcOR54WVB8XJe6%2B0t%2F%2F9duHfSyWk%2FmSXHju2EFUYIxIyddGeAzbXsYHnvYsD%2B3IGz1m%2FsXk9evfZqeni%2Bk8CM7Jj%2B8OfZnOGB14rkdeyQ8eB9KbpPqH7iGe%2B3TSx3N9v3oayPfkOZKnTxka610aCZ6lwFMuLBveN42FfqfB%2BTKYXzYdVvNW0Iez4NXLsyXswajTBqhz8eq75RkK%2FIFx2%2FIhwjxnsXXjwB%2BVJ74G6xvdMhWv20Wi2LGGvb6xJPh2zUIEd0UqydvOnHz8aN1glHLq9mBgd0LZ5XEomBWL%2BwNRRgErJLjAsSmjgrH0ea2pJ%2BAYR6KtTJ0J2VtWPDekKrKukMS2ZRRHnLD2pCesyC05zSpmVVm7fjXOUlOglmi9j2SoDaqY6pCk0WcUjzbvSRHDGWzkPQXHiapQrarqpbAyaouq4r1moZzokrcLVbWKv%2B91eNQqBw2rWhnquw9%2Bp6Tdhrd1MSscVK2kVcizklMBW23aO1bL7IneW08gFq2OVeJVV6zWGCL7BndL1N5W2qfiK5%2FYLnew1o7byCgIP3zQQT2rZW2wVJzH0G%2FSbwKoG%2F22KZpK4UFjcmc%2FaqpOKNqy6HeVT5QlCS9pCq652LYbk1yitGCRUDk6%2BmKpA2g%2Fqdala0qeF7iI7HsCbCWr7pEeXXJfEbUEQElt1SZytXcHOS7C63pvRUlpefaji4W2VJjgurXMiKWCFaYD5hUnCEy7o1Vikla11LoiSy9Gp9qGaGM5%2BQGWM3U1oM2hfbeWqvVPaEKbcAjqNU8S60sw6edBbUdmx4tI6j5Ummp3PJDBfRhIhAiCbZhueLq5g4Hl4%2B1F%2F%2Bbfn%2F%2F8569PYLoFy1koVDa2A6P77bI0fsRqtW8q65Zp4jTJ7rBdzHtBUVl4E3v2XST%2F70piVAO8Xc2Xk%2B8DmP0UzM1ODb4wh%2FvEc5%2BO%2F8tqT6G4mM7mwREl%2B1UpY6f%2FC73Is6Q%3D)
90
+ - [Bouncing Ball](https://litecanvas.js.org?c=eJxtUstugzAQvPMVewTiBCdtpVYpPVStSu6ReraMiSy5gIyhRUn%2BvQZvg0NzsGTveGdmH0oYqKtGGlmVkEIneLimlNgTkQDsW1Vcmn64HzTr8KpZLtvG%2Fr9%2FDAIljeCs7FgTRkFQtCUfyWQpTRjB0WNBgc%2Fd2z6LNyR7331k%2B2QTbSd2%2FEIRtNDZ42zrnBkR5sbx%2Fhlf%2FcAivajYVwy58fH%2BCu8d7hlzBGhhyvcyPHhKTxJ4raw3AVVRgMgPorFRWUA4OVtgr15gLBtOJ8%2F2EsFnoK6iK0txCsv19kaUrp4exrDXgBS4Yl%2B1p0xwTMQpo1ZkE89zl7Y%2BuPjEzs%2F99KOf%2F9LDyFwKKowC%2FtRyzb5xE7hqQjrOm0vNC6mUb3jiJGj9brYAw27YwnrHpoVpdQnHIWIlfwGU6d0i)
91
+ - [Scroller](https://litecanvas.js.org?c=eJxVUMFOwzAMvfcrzAEtacNIxwZDsAPSJoG0AxJIO0w7hDZdI6XN1HhQgfbvOOs22CGJ7ff8nh1rUGeq%2FlSe8SgqtnWGxtVgaoPAOPxEAJX2Xq01TKC30DZzlQZ0YE%2BNFz0itQQvXqbvzxRvlKVsORQjcSvuxFjci1SKNF1Fu38W202uUAPLsbNB6pnNn17fZlPKvPnWR8nrwwR9q%2Bs1loSWBI3k3vZqAjdSQgw5nsnnjfo6bpBZzySnoHANMKsRDAnIB3oe4VycaknSdQEEJoa5MGGyn8aGn8p0UDcEHdv99sNjw4yA9I%2BVOesCiX5kWVjnGoYYjy4pPdjx1Z6LusWwMQsXP5VYpVrWChhInpg4YLHsjwWUyUAm3tRBbcjjUhzGEZ1hENjRZ%2FwCV%2F6J0w%3D%3D)
92
+ - [3D rojection](https://litecanvas.js.org?c=eJyNVU2P2jAQvfMrpodqnWI%2By6kt7WW3hVulrtTuRjk4JCyG4CDHFJYV%2F73jj5CYDUslROx579njGc844yqdMfGXFSRoteZbMVM8F8AFVySAlxZAxkW644lakGGA04IfUhjD7%2Bnt%2FQS%2BwuRu%2BmNyD9%2FKQQ9G8MmhOEbBRubL1K46hhANAOGAQh9%2FEbVTHA%2F8qbaYaaRXyLlQRaXuaB8o1D8RhV4Pwj2FZwqHyPKaaHWkAehcQ2oEf7GLwGt7pxGIqliliT6sNjDxlOlw91vHWna2m4SplCTKZsiS2sjq9gctNMxzCSRLFXAt%2FYyfLy6I3SwVT2qBpnbbigFmuSgU4jrChjVcMyX5nnS7XasKeRQYql5T5opZD5G23mbEGNCtP8Q4ElC9luVf5D6cuI5yhf%2FYzHeul1Eb3lbK6tqdaU5sPJU%2BcSVG%2BOjFOZFs56pglhWkH5gUYUSIvaLff%2F6iMAp0yDfbYkE0riQTRabTUxbBkFbFYUqoKT2j85QIdIpwCoRDGwYBvIcRrZwNfBpSECU1bmBNlwQUeAPhaIptg%2BeoR6EUMQoxdfeoag3EXREWhX39KxnaMPAM8TkjPjE%2B%2Bju6FOoNXfRNntksz%2FQlZahxF7nCZL4z2DkQO1HcIIqdKC4BRPgciN3o3XhsGQGoBX4B%2ByDcSZlLcjPFjpnxBGylpMWNuQR2VZkWZfk2Zdp46mUbBfYuhrZxnVRLq1qiyhwDh5XKVmOxXZv2YC0n5coqV3o%2Fq1zVlWB0bR1KHoWrCD5gfFZRuIwc4ei%2B1jW043ooaZXYUR9XpmorheZ42av6gWQJx2qw%2Bzr2xRdglhcngWmnojatE33El%2FVdK23y5%2BEtf86W8Xe58E6d%2B%2Bg784Ynj%2F%2FvyaU4XA%2FCq4e07onX592r6TsT7rEuw2f9d4i0%2BB%2FEGGLo)
93
+ - [Rendering Benchmark](https://litecanvas.js.org?c=eJylVu1z2jYY%2F%2B6%2FQkd3Z7shAgOhCQXvctlu61229ZrtE8ddhC1Ai7CoJJK4Kf%2F7HknGGHBot%2BkDSM%2FLT8%2B7nIhMaZSITEvBFRqhVCTrJc00TiQlmv7MqTkFfsoe%2FdDbCmKWZVT%2B%2Budvt6By7w2VzjmNPYJJotkjRS8egjUD6fMnyuYLPUBTwdP33sYbtgrhIUDGVg7Upcjm8fVSrDM9MBL27JgELSSdjRo%2FEsseRe12A6VEk%2FOC0DCUGH6GLfKKzsWRjqHEF6d0ALDmInPTw%2BtKnWMlS4o7J5R6x0qWFPdOKF0eK1lSfHlCKerXONV3XvVP6HVr%2FHK0uFt4NmzZfJqsIpvhUWMlFNNMZAMkKSemMBoFvhFi6aihNNGqERe6lpWQ7JEoy00Mx523%2BPdeWaBTkeZ4JemKZmmwLczwgG8twYngQkKl%2Bm%2BiKPJrRaYkeZhL8Cy9KYVns5nveZxqtJYcKBl9Qn99ug24SIjxK2xak40TFNiu6M3S%2BYoOkK9WkmnqN0t6YusbnQEcVpTIZPGRSLJUeE6hxVxg%2FRB9%2FYqg0nZqTyzViwHQOr0dcVH0Ve%2BykNzsrDGNnK05B%2BPBABfBwJlXYFmbsT04tS2cY7iT4zj1wW4yfF5Tmd9RThMtZOC%2FSWAwbELPm62zxIQFsYzpICziwQVJ7xLJVuDhQuuVGrRaS5kKMcVzphfrKWaiZY3Gf6vWdM14WhyXLAOS30SANYrL8JYOQjbuzL5Kx2ohnj6SjPKgHe4xUrEsUr0tS5NjMlWCryFJheyrTloQP8RkZertZgFmBiWwu8nEoKwH7JIPd6wIgwFa5i3qNGv343Jnlo9h9WCZ%2F0oF7XiEvMJrtw2v3a7ldQxmp4b3DlYX1gWsI16%2F%2F64bRd2L6fTf8U5gGluuYNXZYny4ury8qvcBY8N7LS4G84A32W1f9hRUQjj0aXfH39hdWE0j%2B2KSWM2paxuX61YLQQtrlnCK7IsJdeW6xj6sq4K3rddrKUkeODA7DMKKcMrM2BlHTXQeTYoHVKLAzB8GjPZ7%2BBuiijIQzs7CilMO5xmEJYGhGKK31UZH5xWfDnTyYx03A04pjdPnJkrzibF6L7AWCfzoGjRwa2wILGg3URROmv9FdFLuypCOmbn4BYENOZhhTSkSuKk2YjUH5d6rxJ2z7KH65bPf%2BvfjvVfvh5dKAjaNyb3LIJtBogCnmg1zxgknSt0ypTFJU5jx9vvILwZGaYSAscTFPLj%2FBMOFSpbN0f5FyJWegsqGGzeVYZtK8lQOWyjHGw4PC9ILWgxudwdXZh6Wpt7%2Bcf3Th99%2FCeFh1muZ7cKl8JTOWRaEx%2BVNMrY8Vd8H0f4fBSyrmYI0eyVf4md0DpWK0%2BcKLS9o%2BU7QOGmEh6gdHnS9IYMxB7T0Gb0dQePtRgGiXNECKK8HyuuA8kOgY6vOqsMlrvZovbHfbOLv86H2Wtfm9b59exB8h89LMqfG66YBbe5N0vCwWxU2H3OmwP8BV89smg%3D%3D)
94
94
 
95
95
  _See other demos in [samples](samples) folder_
96
96
 
97
97
  ## Inspirations
98
98
 
99
- - [floppy](https://github.com/lpagg/floppy): a micro game engine for beginners.
100
- - [PICO-8](https://www.lexaloffle.com/pico-8.php): fantasy console for making, sharing and playing tiny games.
101
- - [js13kGames](https://js13kgames.com/): a JavaScript coding competition with size limit set to 13 kilobytes.
102
- - [raylib](https://www.raylib.com/): a simple and easy-to-use gamedev library.
103
- - [p5.js/Processing](https://p5js.org/): a library for creative coding.
99
+ - [floppy](https://github.com/lpagg/floppy): a micro game engine for beginners.
100
+ - [PICO-8](https://www.lexaloffle.com/pico-8.php): fantasy console for making, sharing and playing tiny games.
101
+ - [js13kGames](https://js13kgames.com/): a JavaScript coding competition with size limit set to 13 kilobytes.
102
+ - [raylib](https://www.raylib.com/): a simple and easy-to-use gamedev library.
103
+ - [p5.js/Processing](https://p5js.org/): a library for creative coding.
104
+ - [Pygame Zero](https://github.com/lordmauve/pgzero): A zero-boilerplate games programming framework for Python 3.
package/dist/dist.js CHANGED
@@ -42,7 +42,12 @@
42
42
 
43
43
  // src/index.js
44
44
  function litecanvas(settings = {}) {
45
- const root = globalThis, PI = Math.PI, TWO_PI = PI * 2, raf = requestAnimationFrame, on = (elem, evt, callback) => elem.addEventListener(evt, callback), defaults = {
45
+ const root = globalThis, PI = Math.PI, TWO_PI = PI * 2, raf = requestAnimationFrame, _browerEventListeners = [], on = (elem, evt, callback) => {
46
+ elem.addEventListener(evt, callback, false);
47
+ _browerEventListeners.push(
48
+ () => elem.removeEventListener(evt, callback, false)
49
+ );
50
+ }, defaults = {
46
51
  fps: 60,
47
52
  fullscreen: true,
48
53
  width: null,
@@ -59,7 +64,7 @@
59
64
  animate: true
60
65
  };
61
66
  settings = Object.assign(defaults, settings);
62
- let _initialized = false, _plugins = [], _canvas = settings.canvas || document.createElement("canvas"), _fullscreen = settings.fullscreen, _autoscale = settings.autoscale, _animated = settings.animate, _scale = 1, _ctx, _timeScale = 1, _lastFrame, _step, _stepMs, _accumulated = 0, _focused = true, _drawCount = 0, _nextFpsUpdate = 0, _fontFamily = "sans-serif", _fontStyle = "", _fontSize = 32, _rng_seed = Date.now(), _events = {
67
+ let _initialized = false, _plugins = [], _canvas = settings.canvas || document.createElement("canvas"), _fullscreen = settings.fullscreen, _autoscale = settings.autoscale, _animated = settings.animate, _scale = 1, _ctx, _timeScale = 1, _lastFrame, _step, _stepMs, _accumulated = 0, _focused = true, _fontFamily = "sans-serif", _fontStyle = "", _fontSize = 32, _rng_seed = Date.now(), _global = settings.global, _events = {
63
68
  init: null,
64
69
  update: null,
65
70
  draw: null,
@@ -82,8 +87,6 @@
82
87
  /** @type {number} */
83
88
  ELAPSED: 0,
84
89
  /** @type {number} */
85
- FPS: 0,
86
- /** @type {number} */
87
90
  CENTERX: 0,
88
91
  /** @type {number} */
89
92
  CENTERY: 0,
@@ -668,7 +671,7 @@
668
671
  */
669
672
  setvar(key, value) {
670
673
  instance[key] = value;
671
- if (settings.global) {
674
+ if (_global) {
672
675
  root[key] = value;
673
676
  }
674
677
  },
@@ -702,6 +705,21 @@
702
705
  _step = 1 / fps;
703
706
  _stepMs = _step * 1e3;
704
707
  _accumulated = 0;
708
+ },
709
+ /**
710
+ * Stops the litecanvas instance and remove all event listeners.
711
+ */
712
+ quit() {
713
+ _focused = _events = false;
714
+ for (const removeListener of _browerEventListeners) {
715
+ removeListener();
716
+ }
717
+ if (_global) {
718
+ for (const key in instance) {
719
+ delete root[key];
720
+ }
721
+ }
722
+ delete root.__litecanvas;
705
723
  }
706
724
  };
707
725
  for (const k of [
@@ -727,7 +745,7 @@
727
745
  function init() {
728
746
  _initialized = true;
729
747
  const source = settings.loop ? settings.loop : root;
730
- for (const event of Object.keys(_events)) {
748
+ for (const event in _events) {
731
749
  if (source[event]) instance.listen(event, source[event]);
732
750
  }
733
751
  for (const plugin of _plugins) {
@@ -848,34 +866,27 @@
848
866
  });
849
867
  on(root, "focus", () => {
850
868
  _lastFrame = performance.now();
851
- raf(drawFrame);
852
869
  _focused = true;
870
+ raf(drawFrame);
853
871
  });
854
872
  }
855
873
  instance.setfps(settings.fps);
856
874
  instance.emit("init", instance);
857
875
  _lastFrame = performance.now();
858
- _nextFpsUpdate = _lastFrame + 1e3;
859
876
  raf(drawFrame);
860
877
  }
861
878
  function drawFrame(now) {
862
- let emitDraw = !_animated, dt = now - _lastFrame;
863
- _accumulated += dt > 1e3 ? _stepMs : dt;
879
+ let emitDraw = !_animated, delta = now - _lastFrame;
880
+ _accumulated += delta > 1e3 ? _stepMs : delta;
864
881
  while (_accumulated >= _stepMs) {
865
882
  instance.emit("update", _step * _timeScale);
866
883
  instance.setvar("ELAPSED", instance.ELAPSED + _step * _timeScale);
867
884
  _accumulated -= _stepMs;
868
- emitDraw = true;
885
+ emitDraw = 1;
869
886
  }
870
887
  if (emitDraw) {
871
888
  instance.textalign("start", "top");
872
889
  instance.emit("draw");
873
- _drawCount++;
874
- if (now + _accumulated > _nextFpsUpdate) {
875
- instance.setvar("FPS", _drawCount);
876
- _drawCount = 0;
877
- _nextFpsUpdate = now + 1e3;
878
- }
879
890
  }
880
891
  _lastFrame = now;
881
892
  if (_focused && _animated) {
@@ -890,30 +901,28 @@
890
901
  if (instance.WIDTH > 0) {
891
902
  _fullscreen = false;
892
903
  }
904
+ _canvas.style = "";
893
905
  _canvas.width = instance.WIDTH;
894
906
  _canvas.height = instance.HEIGHT || instance.WIDTH;
895
907
  if (!_canvas.parentNode) document.body.appendChild(_canvas);
896
- _canvas.style.display = "block";
897
- if (_fullscreen) {
898
- _canvas.style.position = "absolute";
899
- _canvas.style.inset = 0;
900
- } else if (_autoscale) {
901
- _canvas.style.margin = "auto";
902
- }
903
908
  }
904
909
  function pageResized() {
905
- const pageWidth = root.innerWidth, pageHeight = root.innerHeight;
910
+ const pageWidth = root.innerWidth, pageHeight = root.innerHeight, styles = _canvas.style;
911
+ styles.display = "block";
906
912
  if (_fullscreen) {
913
+ styles.position = "absolute";
914
+ styles.inset = 0;
907
915
  instance.setvar("WIDTH", _canvas.width = pageWidth);
908
916
  instance.setvar("HEIGHT", _canvas.height = pageHeight);
909
917
  } else if (_autoscale) {
918
+ styles.margin = "auto";
910
919
  _scale = Math.min(
911
920
  pageWidth / instance.WIDTH,
912
921
  pageHeight / instance.HEIGHT
913
922
  );
914
923
  _scale = (settings.pixelart ? ~~_scale : _scale) || 1;
915
- _canvas.style.width = instance.WIDTH * _scale + "px";
916
- _canvas.style.height = instance.HEIGHT * _scale + "px";
924
+ styles.width = instance.WIDTH * _scale + "px";
925
+ styles.height = instance.HEIGHT * _scale + "px";
917
926
  }
918
927
  instance.setvar("CENTERX", instance.WIDTH / 2);
919
928
  instance.setvar("CENTERY", instance.HEIGHT / 2);
@@ -940,7 +949,7 @@
940
949
  }
941
950
  }
942
951
  }
943
- if (settings.global) {
952
+ if (_global) {
944
953
  if (root.__litecanvas) {
945
954
  throw "global litecanvas already instantiated";
946
955
  }
package/dist/dist.min.js CHANGED
@@ -1 +1 @@
1
- (()=>{var e=new AudioContext,t=(t=1,a=.05,l=220,n=0,i=0,r=.1,o=0,s=1,c=0,f=0,u=0,p=0,d=0,g=0,h=0,m=0,v=0,T=1,b=0,x=0,E=0)=>{let y=Math,w=2*y.PI,H=c*=500*w/44100/44100,D=l*=(1-a+2*a*y.random(a=[]))*w/44100,I=0,S=0,k=0,A=1,C=0,O=0,P=0,X=E<0?-1:1,L=w*X*E*2/44100,W=y.cos(L),z=y.sin,M=z(L)/4,Y=1+M,F=-2*W/Y,_=(1-M)/Y,B=(1+X*W)/2/Y,G=-(X+W)/Y,R=0,N=0,j=0,U=0;for(n=44100*n+9,b*=44100,i*=44100,r*=44100,v*=44100,f*=500*w/85766121e6,h*=w/44100,u*=w/44100,p*=44100,d=44100*d|0,t*=.3*(globalThis.zzfxV||1),X=n+b+i+r+v|0;k<X;a[k++]=P*t)++O%(100*m|0)||(P=o?1<o?2<o?3<o?z(I*I):y.max(y.min(y.tan(I),1),-1):1-(2*I/w%2+2)%2:1-4*y.abs(y.round(I/w)-I/w):z(I),P=(d?1-x+x*z(w*k/d):1)*(P<0?-1:1)*y.abs(P)**s*(k<n?k/n:k<n+b?1-(k-n)/b*(1-T):k<n+b+i?T:k<X-v?(X-k-v)/r*T:0),P=v?P/2+(v>k?0:(k<X-v?1:(X-k)/v)*a[k-v|0]/2/t):P,E&&(P=U=B*R+G*(R=N)+B*(N=P)-_*j-F*(j=U))),I+=(L=(l+=c+=f)*y.cos(h*S++))+L*g*z(k**5),A&&++A>p&&(l+=u,D+=u,A=0),!d||++C%d||(l=D,c=H,A=A||1);(t=e.createBuffer(1,X,44100)).getChannelData(0).set(a),(l=e.createBufferSource()).buffer=t,l.connect(e.destination),l.start()},a=["#111","#6a7799","#aec2c2","#FFF1E8","#e83b3b","#fabc20","#155fd9","#3cbcfc","#327345","#63c64d","#6c2c1f","#ac7c00"];globalThis.litecanvas=function(e={}){let l=globalThis,n=Math.PI,i=2*n,r=requestAnimationFrame,o=(e,t,a)=>e.addEventListener(t,a);e=Object.assign({fps:60,fullscreen:!0,width:null,height:null,autoscale:!0,pixelart:!1,antialias:!1,canvas:null,global:!0,loop:null,pauseOnBlur:!0,tapEvents:!0,keyboardEvents:!0,animate:!0},e);let s=!1,c=[],f=e.canvas||document.createElement("canvas"),u=e.fullscreen,p=e.autoscale,d=e.animate,g=1,h,m=1,v,T,b,x=0,E=!0,y=0,w=0,H="sans-serif",D="",I=32,S=Date.now(),k={init:null,update:null,draw:null,resized:null,tap:null,untap:null,tapping:null,tapped:null},A={settings:Object.assign({},e),colors:a},C={WIDTH:e.width,HEIGHT:e.height||e.width,CANVAS:null,ELAPSED:0,FPS:0,CENTERX:0,CENTERY:0,MOUSEX:-1,MOUSEY:-1,DEFAULT_SFX:[.5,,1675,,.06,.2,1,1.8,,,637,.06],PI:n,TWO_PI:i,HALF_PI:.5*n,lerp:(e,t,a)=>e+a*(t-e),deg2rad:e=>n/180*e,rad2deg:e=>180/n*e,clamp:(e,t,a)=>e<t?t:e>a?a:e,wrap:(e,t,a)=>e-(a-t)*Math.floor((e-t)/(a-t)),map(e,t,a,l,n,i){let r=(e-t)/(a-t)*(n-l)+l;return i?C.clamp(r,l,n):r},norm:(e,t,a)=>C.map(e,t,a,0,1),rand:(e=0,t=1)=>(S=(1664525*S+0x3c6ef35f)%0x100000000)/0x100000000*(t-e)+e,randi:(e=0,t=1)=>Math.floor(C.rand(e,t+1)),seed:e=>null==e?S:S=~~e,cls(e){null==e?h.clearRect(0,0,C.WIDTH,C.HEIGHT):C.rectfill(0,0,C.WIDTH,C.HEIGHT,e)},rect(e,t,a,l,n=0,i=null){h.beginPath(),h[i?"roundRect":"rect"](~~e,~~t,a,l,i),C.stroke(n)},rectfill(e,t,a,l,n=0,i=null){h.beginPath(),h[i?"roundRect":"rect"](~~e,~~t,a,l,i),C.fill(n)},circ(e,t,a,l){h.beginPath(),h.arc(~~e,~~t,a,0,i),C.stroke(l)},circfill(e,t,a,l){h.beginPath(),h.arc(~~e,~~t,a,0,i),C.fill(l)},line(e,t,a,l,n){h.beginPath(),h.moveTo(~~e,~~t),h.lineTo(~~a,~~l),C.stroke(n)},linewidth(e){h.lineWidth=e},linedash(e,t=0){h.setLineDash(e),h.lineDashOffset=t},text(e,t,a,l=3){h.font=`${D} ${I}px ${H}`,h.fillStyle=C.getcolor(l),h.fillText(a,~~e,~~t)},textfont(e){H=e},textsize(e){I=e},textstyle(e){D=e||""},textalign(e,t){e&&(h.textAlign=e),t&&(h.textBaseline=t)},textmetrics(e,t=I){h.font=`${D} ${t}px ${H}`;let a=h.measureText(e);return a.height=a.actualBoundingBoxAscent+a.actualBoundingBoxDescent,a},image(e,t,a){h.drawImage(a,~~e,~~t)},paint(e,t,a,l={}){let n=l.canvas||new OffscreenCanvas(1,1),i=l.scale||1,r=h;if(n.width=e*i,n.height=t*i,(h=n.getContext("2d")).scale(i,i),a.push){let e=0,t=0;for(let l of(h.imageSmoothingEnabled=!1,a)){for(let a of l)" "!==a&&"."!==a&&C.rectfill(e,t,1,1,parseInt(a,16)),e++;t++,e=0}}else a(h);return h=r,n},ctx:e=>(e&&(h=e),h),push:()=>h.save(),pop:()=>h.restore(),translate:(e,t)=>h.translate(~~e,~~t),scale:(e,t)=>h.scale(e,t||e),rotate:e=>h.rotate(e),transform:(e,t,a,l,n,i,r=!0)=>h[r?"setTransform":"transform"](e,t,a,l,n,i),alpha(e){h.globalAlpha=C.clamp(e,0,1)},path:e=>new Path2D(e),fill(e,t){h.fillStyle=C.getcolor(e),t?h.fill(t):h.fill()},stroke(e,t){h.strokeStyle=C.getcolor(e),t?h.stroke(t):h.stroke()},clip(e){h.clip(e)},sfx:(e,a=0,n=1)=>!(l.zzfxV<=0)&&(!navigator.userActivation||!!navigator.userActivation.hasBeenActive)&&(e=e||C.DEFAULT_SFX,(a>0||1!==n)&&((e=e.slice())[0]=n*(e[0]||1),e[10]=~~e[10]+a),t.apply(0,e),e),volume(e){l.zzfxV=+e},colrect:(e,t,a,l,n,i,r,o)=>e<n+r&&e+a>n&&t<i+o&&t+l>i,colcirc:(e,t,a,l,n,i)=>(l-e)**2+(n-t)**2<=(a+i)**2,use(e,t={}){e.__conf=t,s?W(e):c.push(e)},listen:(e,t)=>(k[e]=k[e]||new Set,k[e].add(t),()=>k[e].delete(t)),emit(e,t,a,l,n){L("before:"+e,t,a,l,n),L(e,t,a,l,n),L("after:"+e,t,a,l,n)},getcolor:e=>a[~~e%a.length],setvar(t,a){C[t]=a,e.global&&(l[t]=a)},resize(e,t){C.setvar("WIDTH",f.width=e),C.setvar("HEIGHT",f.height=t||e),X()},timescale(e){m=e},setfps(e){b=1e3*(T=1/e),x=0}};for(let e of["sin","cos","atan2","hypot","tan","abs","ceil","round","floor","trunc","min","max","pow","sqrt","sign","exp"])C[e]=Math[e];function O(){s=!0;let t=e.loop?e.loop:l;for(let e of Object.keys(k))t[e]&&C.listen(e,t[e]);for(let e of c)W(e);if((u||p)&&o(l,"resize",X),X(),e.tapEvents){let e=(e,t)=>[(e-f.offsetLeft)/g,(t-f.offsetTop)/g],t=new Map,a=(e,a,l)=>{let n={x:a,y:l,startX:a,startY:l,ts:performance.now()};return t.set(e,n),n},n=(e,l,n)=>{let i=t.get(e)||a(e);i.x=l,i.y=n},i=e=>e&&performance.now()-e.ts<=200,r=!1;o(f,"mousedown",t=>{t.preventDefault();let[l,n]=e(t.pageX,t.pageY);C.emit("tap",l,n,0),a(0,l,n),r=!0}),o(f,"mousemove",t=>{t.preventDefault();let[a,l]=e(t.pageX,t.pageY);C.setvar("MOUSEX",a),C.setvar("MOUSEY",l),r&&(C.emit("tapping",a,l,0),n(0,a,l))}),o(f,"mouseup",a=>{a.preventDefault();let l=t.get(0),[n,o]=e(a.pageX,a.pageY);i(l)&&C.emit("tapped",l.startX,l.startY,0),C.emit("untap",n,o,0),t.delete(0),r=!1}),o(f,"touchstart",t=>{for(let l of(t.preventDefault(),t.changedTouches)){let[t,n]=e(l.pageX,l.pageY);C.emit("tap",t,n,l.identifier+1),a(l.identifier+1,t,n)}}),o(f,"touchmove",t=>{for(let a of(t.preventDefault(),t.changedTouches)){let[t,l]=e(a.pageX,a.pageY);C.emit("tapping",t,l,a.identifier+1),n(a.identifier+1,t,l)}});let s=e=>{e.preventDefault();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)&&C.emit("tapped",l.startX,l.startY,e),C.emit("untap",l.x,l.y,e),t.delete(e))};o(f,"touchend",s),o(f,"touchcancel",s),o(l,"blur",()=>{for(let[e,a]of(r=!1,t))C.emit("untap",a.x,a.y,e),t.delete(e)})}if(e.keyboardEvents){let e=new Set;C.setvar("iskeydown",t=>"any"===t?e.size>0:e.has(t.toLowerCase())),o(l,"keydown",t=>{e.add(t.key.toLowerCase())}),o(l,"keyup",t=>{e.delete(t.key.toLowerCase())}),o(l,"blur",()=>e.clear())}e.pauseOnBlur&&(o(l,"blur",()=>{E=!1}),o(l,"focus",()=>{v=performance.now(),r(P),E=!0})),C.setfps(e.fps),C.emit("init",C),w=(v=performance.now())+1e3,r(P)}function P(e){let t=!d,a=e-v;for(x+=a>1e3?b:a;x>=b;)C.emit("update",T*m),C.setvar("ELAPSED",C.ELAPSED+T*m),x-=b,t=!0;t&&(C.textalign("start","top"),C.emit("draw"),y++,e+x>w&&(C.setvar("FPS",y),y=0,w=e+1e3)),v=e,E&&d&&r(P)}function X(){let t=l.innerWidth,a=l.innerHeight;u?(C.setvar("WIDTH",f.width=t),C.setvar("HEIGHT",f.height=a)):p&&(g=Math.min(t/C.WIDTH,a/C.HEIGHT),g=(e.pixelart?~~g:g)||1,f.style.width=C.WIDTH*g+"px",f.style.height=C.HEIGHT*g+"px"),C.setvar("CENTERX",C.WIDTH/2),C.setvar("CENTERY",C.HEIGHT/2),(!e.antialias||e.pixelart)&&(h.imageSmoothingEnabled=!1,f.style.imageRendering="pixelated"),C.emit("resized",g),d||r(P)}function L(e,t,a,l,n){if(k[e])for(let i of k[e])i(t,a,l,n)}function W(e){let t=e(C,A,e.__conf);if("object"==typeof t)for(let e of Object.keys(t))C.setvar(e,t[e])}if(e.global){if(l.__litecanvas)throw"global litecanvas already instantiated";Object.assign(l,C),l.__litecanvas=C}return f="string"==typeof f?document.querySelector(f):f,C.setvar("CANVAS",f),h=f.getContext("2d"),o(f,"click",()=>l.focus()),C.WIDTH>0&&(u=!1),f.width=C.WIDTH,f.height=C.HEIGHT||C.WIDTH,f.parentNode||document.body.appendChild(f),f.style.display="block",u?(f.style.position="absolute",f.style.inset=0):p&&(f.style.margin="auto"),"loading"===document.readyState?o(l,"DOMContentLoaded",()=>r(O)):r(O),C}})();
1
+ (()=>{var e=new AudioContext,t=(t=1,a=.05,l=220,n=0,i=0,r=.1,o=0,s=1,c=0,f=0,u=0,p=0,d=0,g=0,h=0,m=0,v=0,E=1,T=0,x=0,b=0)=>{let w=Math,H=2*w.PI,D=c*=500*H/44100/44100,y=l*=(1-a+2*a*w.random(a=[]))*H/44100,I=0,S=0,A=0,k=1,C=0,L=0,X=0,O=b<0?-1:1,W=H*O*b*2/44100,_=w.cos(W),z=w.sin,M=z(W)/4,P=1+M,Y=-2*_/P,B=(1-M)/P,F=(1+O*_)/2/P,G=-(O+_)/P,R=0,N=0,U=0,$=0;for(n=44100*n+9,T*=44100,i*=44100,r*=44100,v*=44100,f*=500*H/85766121e6,h*=H/44100,u*=H/44100,p*=44100,d=44100*d|0,t*=.3*(globalThis.zzfxV||1),O=n+T+i+r+v|0;A<O;a[A++]=X*t)++L%(100*m|0)||(X=o?1<o?2<o?3<o?z(I*I):w.max(w.min(w.tan(I),1),-1):1-(2*I/H%2+2)%2:1-4*w.abs(w.round(I/H)-I/H):z(I),X=(d?1-x+x*z(H*A/d):1)*(X<0?-1:1)*w.abs(X)**s*(A<n?A/n:A<n+T?1-(A-n)/T*(1-E):A<n+T+i?E:A<O-v?(O-A-v)/r*E:0),X=v?X/2+(v>A?0:(A<O-v?1:(O-A)/v)*a[A-v|0]/2/t):X,b&&(X=$=F*R+G*(R=N)+F*(N=X)-B*U-Y*(U=$))),I+=(W=(l+=c+=f)*w.cos(h*S++))+W*g*z(A**5),k&&++k>p&&(l+=u,y+=u,k=0),!d||++C%d||(l=y,c=D,k=k||1);(t=e.createBuffer(1,O,44100)).getChannelData(0).set(a),(l=e.createBufferSource()).buffer=t,l.connect(e.destination),l.start()},a=["#111","#6a7799","#aec2c2","#FFF1E8","#e83b3b","#fabc20","#155fd9","#3cbcfc","#327345","#63c64d","#6c2c1f","#ac7c00"];globalThis.litecanvas=function(e={}){let l=globalThis,n=Math.PI,i=2*n,r=requestAnimationFrame,o=[],s=(e,t,a)=>{e.addEventListener(t,a,!1),o.push(()=>e.removeEventListener(t,a,!1))};e=Object.assign({fps:60,fullscreen:!0,width:null,height:null,autoscale:!0,pixelart:!1,antialias:!1,canvas:null,global:!0,loop:null,pauseOnBlur:!0,tapEvents:!0,keyboardEvents:!0,animate:!0},e);let c=!1,f=[],u=e.canvas||document.createElement("canvas"),p=e.fullscreen,d=e.autoscale,g=e.animate,h=1,m,v=1,E,T,x,b=0,w=!0,H="sans-serif",D="",y=32,I=Date.now(),S=e.global,A={init:null,update:null,draw:null,resized:null,tap:null,untap:null,tapping:null,tapped:null},k={settings:Object.assign({},e),colors:a},C={WIDTH:e.width,HEIGHT:e.height||e.width,CANVAS:null,ELAPSED:0,CENTERX:0,CENTERY:0,MOUSEX:-1,MOUSEY:-1,DEFAULT_SFX:[.5,,1675,,.06,.2,1,1.8,,,637,.06],PI:n,TWO_PI:i,HALF_PI:.5*n,lerp:(e,t,a)=>e+a*(t-e),deg2rad:e=>n/180*e,rad2deg:e=>180/n*e,clamp:(e,t,a)=>e<t?t:e>a?a:e,wrap:(e,t,a)=>e-(a-t)*Math.floor((e-t)/(a-t)),map(e,t,a,l,n,i){let r=(e-t)/(a-t)*(n-l)+l;return i?C.clamp(r,l,n):r},norm:(e,t,a)=>C.map(e,t,a,0,1),rand:(e=0,t=1)=>(I=(1664525*I+0x3c6ef35f)%0x100000000)/0x100000000*(t-e)+e,randi:(e=0,t=1)=>Math.floor(C.rand(e,t+1)),seed:e=>null==e?I:I=~~e,cls(e){null==e?m.clearRect(0,0,C.WIDTH,C.HEIGHT):C.rectfill(0,0,C.WIDTH,C.HEIGHT,e)},rect(e,t,a,l,n=0,i=null){m.beginPath(),m[i?"roundRect":"rect"](~~e,~~t,a,l,i),C.stroke(n)},rectfill(e,t,a,l,n=0,i=null){m.beginPath(),m[i?"roundRect":"rect"](~~e,~~t,a,l,i),C.fill(n)},circ(e,t,a,l){m.beginPath(),m.arc(~~e,~~t,a,0,i),C.stroke(l)},circfill(e,t,a,l){m.beginPath(),m.arc(~~e,~~t,a,0,i),C.fill(l)},line(e,t,a,l,n){m.beginPath(),m.moveTo(~~e,~~t),m.lineTo(~~a,~~l),C.stroke(n)},linewidth(e){m.lineWidth=e},linedash(e,t=0){m.setLineDash(e),m.lineDashOffset=t},text(e,t,a,l=3){m.font=`${D} ${y}px ${H}`,m.fillStyle=C.getcolor(l),m.fillText(a,~~e,~~t)},textfont(e){H=e},textsize(e){y=e},textstyle(e){D=e||""},textalign(e,t){e&&(m.textAlign=e),t&&(m.textBaseline=t)},textmetrics(e,t=y){m.font=`${D} ${t}px ${H}`;let a=m.measureText(e);return a.height=a.actualBoundingBoxAscent+a.actualBoundingBoxDescent,a},image(e,t,a){m.drawImage(a,~~e,~~t)},paint(e,t,a,l={}){let n=l.canvas||new OffscreenCanvas(1,1),i=l.scale||1,r=m;if(n.width=e*i,n.height=t*i,(m=n.getContext("2d")).scale(i,i),a.push){let e=0,t=0;for(let l of(m.imageSmoothingEnabled=!1,a)){for(let a of l)" "!==a&&"."!==a&&C.rectfill(e,t,1,1,parseInt(a,16)),e++;t++,e=0}}else a(m);return m=r,n},ctx:e=>(e&&(m=e),m),push:()=>m.save(),pop:()=>m.restore(),translate:(e,t)=>m.translate(~~e,~~t),scale:(e,t)=>m.scale(e,t||e),rotate:e=>m.rotate(e),transform:(e,t,a,l,n,i,r=!0)=>m[r?"setTransform":"transform"](e,t,a,l,n,i),alpha(e){m.globalAlpha=C.clamp(e,0,1)},path:e=>new Path2D(e),fill(e,t){m.fillStyle=C.getcolor(e),t?m.fill(t):m.fill()},stroke(e,t){m.strokeStyle=C.getcolor(e),t?m.stroke(t):m.stroke()},clip(e){m.clip(e)},sfx:(e,a=0,n=1)=>!(l.zzfxV<=0)&&(!navigator.userActivation||!!navigator.userActivation.hasBeenActive)&&(e=e||C.DEFAULT_SFX,(a>0||1!==n)&&((e=e.slice())[0]=n*(e[0]||1),e[10]=~~e[10]+a),t.apply(0,e),e),volume(e){l.zzfxV=+e},colrect:(e,t,a,l,n,i,r,o)=>e<n+r&&e+a>n&&t<i+o&&t+l>i,colcirc:(e,t,a,l,n,i)=>(l-e)**2+(n-t)**2<=(a+i)**2,use(e,t={}){e.__conf=t,c?_(e):f.push(e)},listen:(e,t)=>(A[e]=A[e]||new Set,A[e].add(t),()=>A[e].delete(t)),emit(e,t,a,l,n){W("before:"+e,t,a,l,n),W(e,t,a,l,n),W("after:"+e,t,a,l,n)},getcolor:e=>a[~~e%a.length],setvar(e,t){C[e]=t,S&&(l[e]=t)},resize(e,t){C.setvar("WIDTH",u.width=e),C.setvar("HEIGHT",u.height=t||e),O()},timescale(e){v=e},setfps(e){x=1e3*(T=1/e),b=0},quit(){for(let e of(w=A=!1,o))e();if(S)for(let e in C)delete l[e];delete l.__litecanvas}};for(let e of["sin","cos","atan2","hypot","tan","abs","ceil","round","floor","trunc","min","max","pow","sqrt","sign","exp"])C[e]=Math[e];function L(){c=!0;let t=e.loop?e.loop:l;for(let e in A)t[e]&&C.listen(e,t[e]);for(let e of f)_(e);if((p||d)&&s(l,"resize",O),O(),e.tapEvents){let e=(e,t)=>[(e-u.offsetLeft)/h,(t-u.offsetTop)/h],t=new Map,a=(e,a,l)=>{let n={x:a,y:l,startX:a,startY:l,ts:performance.now()};return t.set(e,n),n},n=(e,l,n)=>{let i=t.get(e)||a(e);i.x=l,i.y=n},i=e=>e&&performance.now()-e.ts<=200,r=!1;s(u,"mousedown",t=>{t.preventDefault();let[l,n]=e(t.pageX,t.pageY);C.emit("tap",l,n,0),a(0,l,n),r=!0}),s(u,"mousemove",t=>{t.preventDefault();let[a,l]=e(t.pageX,t.pageY);C.setvar("MOUSEX",a),C.setvar("MOUSEY",l),r&&(C.emit("tapping",a,l,0),n(0,a,l))}),s(u,"mouseup",a=>{a.preventDefault();let l=t.get(0),[n,o]=e(a.pageX,a.pageY);i(l)&&C.emit("tapped",l.startX,l.startY,0),C.emit("untap",n,o,0),t.delete(0),r=!1}),s(u,"touchstart",t=>{for(let l of(t.preventDefault(),t.changedTouches)){let[t,n]=e(l.pageX,l.pageY);C.emit("tap",t,n,l.identifier+1),a(l.identifier+1,t,n)}}),s(u,"touchmove",t=>{for(let a of(t.preventDefault(),t.changedTouches)){let[t,l]=e(a.pageX,a.pageY);C.emit("tapping",t,l,a.identifier+1),n(a.identifier+1,t,l)}});let o=e=>{e.preventDefault();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)&&C.emit("tapped",l.startX,l.startY,e),C.emit("untap",l.x,l.y,e),t.delete(e))};s(u,"touchend",o),s(u,"touchcancel",o),s(l,"blur",()=>{for(let[e,a]of(r=!1,t))C.emit("untap",a.x,a.y,e),t.delete(e)})}if(e.keyboardEvents){let e=new Set;C.setvar("iskeydown",t=>"any"===t?e.size>0:e.has(t.toLowerCase())),s(l,"keydown",t=>{e.add(t.key.toLowerCase())}),s(l,"keyup",t=>{e.delete(t.key.toLowerCase())}),s(l,"blur",()=>e.clear())}e.pauseOnBlur&&(s(l,"blur",()=>{w=!1}),s(l,"focus",()=>{E=performance.now(),w=!0,r(X)})),C.setfps(e.fps),C.emit("init",C),E=performance.now(),r(X)}function X(e){let t=!g,a=e-E;for(b+=a>1e3?x:a;b>=x;)C.emit("update",T*v),C.setvar("ELAPSED",C.ELAPSED+T*v),b-=x,t=1;t&&(C.textalign("start","top"),C.emit("draw")),E=e,w&&g&&r(X)}function O(){let t=l.innerWidth,a=l.innerHeight,n=u.style;n.display="block",p?(n.position="absolute",n.inset=0,C.setvar("WIDTH",u.width=t),C.setvar("HEIGHT",u.height=a)):d&&(n.margin="auto",h=Math.min(t/C.WIDTH,a/C.HEIGHT),h=(e.pixelart?~~h:h)||1,n.width=C.WIDTH*h+"px",n.height=C.HEIGHT*h+"px"),C.setvar("CENTERX",C.WIDTH/2),C.setvar("CENTERY",C.HEIGHT/2),(!e.antialias||e.pixelart)&&(m.imageSmoothingEnabled=!1,u.style.imageRendering="pixelated"),C.emit("resized",h),g||r(X)}function W(e,t,a,l,n){if(A[e])for(let i of A[e])i(t,a,l,n)}function _(e){let t=e(C,k,e.__conf);if("object"==typeof t)for(let e of Object.keys(t))C.setvar(e,t[e])}if(S){if(l.__litecanvas)throw"global litecanvas already instantiated";Object.assign(l,C),l.__litecanvas=C}return u="string"==typeof u?document.querySelector(u):u,C.setvar("CANVAS",u),m=u.getContext("2d"),s(u,"click",()=>l.focus()),C.WIDTH>0&&(p=!1),u.style="",u.width=C.WIDTH,u.height=C.HEIGHT||C.WIDTH,u.parentNode||document.body.appendChild(u),"loading"===document.readyState?s(l,"DOMContentLoaded",()=>r(L)):r(L),C}})();
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "litecanvas",
3
- "version": "0.67.2",
3
+ "version": "0.68.0",
4
4
  "description": "Lightweight HTML5 canvas engine suitable for small games and animations.",
5
5
  "license": "MIT",
6
6
  "author": "Luiz Bills <luizbills@pm.me>",
7
7
  "contributors": [],
8
8
  "devDependencies": {
9
+ "@swc/core": "^1.7.26",
9
10
  "ava": "^6.1.3",
10
11
  "esbuild": "^0.24.0",
11
- "@swc/core": "^1.7.26"
12
+ "prettier": "^3.4.2"
12
13
  },
13
14
  "homepage": "https://litecanvas.github.io/about.html",
14
15
  "repository": {
@@ -26,8 +27,8 @@
26
27
  "dev:test": "ava --watch",
27
28
  "dev": "esbuild src/web.js --bundle --watch --outfile=dist/dist.js --servedir=.",
28
29
  "build": "node script/build.js",
29
- "gzip-size": "gzip -9 -c dist/dist.min.js | wc -c | xargs printf \" Gzip size: %s bytes\n\"",
30
- "format": "npx prettier -w src/* samples/* types/*",
30
+ "gzip-size": "gzip -c dist/dist.min.js | wc -c | xargs printf \" Gzip size: %s bytes\n\"",
31
+ "format": "prettier -w src/* samples/* types/* script/* types/*",
31
32
  "prepare": "npm run build && npm run gzip-size",
32
33
  "prepublishOnly": "npm test"
33
34
  },
package/src/index.js CHANGED
@@ -13,8 +13,15 @@ export default function litecanvas(settings = {}) {
13
13
  PI = Math.PI,
14
14
  TWO_PI = PI * 2,
15
15
  raf = requestAnimationFrame,
16
+ /** @type {Function[]} */
17
+ _browerEventListeners = [],
16
18
  /** @type {(elem:HTMLElement, evt:string, callback:(event:Event)=>void)=>void} */
17
- on = (elem, evt, callback) => elem.addEventListener(evt, callback),
19
+ on = (elem, evt, callback) => {
20
+ elem.addEventListener(evt, callback, false)
21
+ _browerEventListeners.push(() =>
22
+ elem.removeEventListener(evt, callback, false)
23
+ )
24
+ },
18
25
  /** @type {LitecanvasOptions} */
19
26
  defaults = {
20
27
  fps: 60,
@@ -64,10 +71,6 @@ export default function litecanvas(settings = {}) {
64
71
  _accumulated = 0,
65
72
  /** @type {number} */
66
73
  _focused = true,
67
- /** @type {number} */
68
- _drawCount = 0,
69
- /** @type {number} */
70
- _nextFpsUpdate = 0,
71
74
  /** @type {string} */
72
75
  _fontFamily = 'sans-serif',
73
76
  /** @type {string} */
@@ -76,6 +79,8 @@ export default function litecanvas(settings = {}) {
76
79
  _fontSize = 32,
77
80
  /** @type {number} */
78
81
  _rng_seed = Date.now(),
82
+ /** @type {boolean} */
83
+ _global = settings.global,
79
84
  /**
80
85
  * default game events
81
86
  * @type {Object<string,Set<Function>>}
@@ -114,9 +119,6 @@ export default function litecanvas(settings = {}) {
114
119
  /** @type {number} */
115
120
  ELAPSED: 0,
116
121
 
117
- /** @type {number} */
118
- FPS: 0,
119
-
120
122
  /** @type {number} */
121
123
  CENTERX: 0,
122
124
 
@@ -789,7 +791,7 @@ export default function litecanvas(settings = {}) {
789
791
  */
790
792
  setvar(key, value) {
791
793
  instance[key] = value
792
- if (settings.global) {
794
+ if (_global) {
793
795
  root[key] = value
794
796
  }
795
797
  },
@@ -827,6 +829,22 @@ export default function litecanvas(settings = {}) {
827
829
  _stepMs = _step * 1000
828
830
  _accumulated = 0
829
831
  },
832
+
833
+ /**
834
+ * Stops the litecanvas instance and remove all event listeners.
835
+ */
836
+ quit() {
837
+ _focused = _events = false
838
+ for (const removeListener of _browerEventListeners) {
839
+ removeListener()
840
+ }
841
+ if (_global) {
842
+ for (const key in instance) {
843
+ delete root[key]
844
+ }
845
+ }
846
+ delete root.__litecanvas
847
+ },
830
848
  }
831
849
 
832
850
  /** Copy some functions from native `Math` object */
@@ -857,7 +875,7 @@ export default function litecanvas(settings = {}) {
857
875
 
858
876
  // add listeners for default events
859
877
  const source = settings.loop ? settings.loop : root
860
- for (const event of Object.keys(_events)) {
878
+ for (const event in _events) {
861
879
  if (source[event]) instance.listen(event, source[event])
862
880
  }
863
881
 
@@ -1025,8 +1043,8 @@ export default function litecanvas(settings = {}) {
1025
1043
 
1026
1044
  on(root, 'focus', () => {
1027
1045
  _lastFrame = performance.now()
1028
- raf(drawFrame)
1029
1046
  _focused = true
1047
+ raf(drawFrame)
1030
1048
  })
1031
1049
  }
1032
1050
 
@@ -1036,7 +1054,6 @@ export default function litecanvas(settings = {}) {
1036
1054
  instance.emit('init', instance)
1037
1055
 
1038
1056
  _lastFrame = performance.now()
1039
- _nextFpsUpdate = _lastFrame + 1000
1040
1057
  raf(drawFrame)
1041
1058
  }
1042
1059
 
@@ -1045,30 +1062,20 @@ export default function litecanvas(settings = {}) {
1045
1062
  */
1046
1063
  function drawFrame(now) {
1047
1064
  let emitDraw = !_animated,
1048
- dt = now - _lastFrame
1065
+ delta = now - _lastFrame
1049
1066
 
1050
- _accumulated += dt > 1000 ? _stepMs : dt
1067
+ _accumulated += delta > 1000 ? _stepMs : delta
1051
1068
 
1052
1069
  while (_accumulated >= _stepMs) {
1053
1070
  instance.emit('update', _step * _timeScale)
1054
1071
  instance.setvar('ELAPSED', instance.ELAPSED + _step * _timeScale)
1055
1072
  _accumulated -= _stepMs
1056
- emitDraw = true
1073
+ emitDraw = 1
1057
1074
  }
1058
1075
 
1059
1076
  if (emitDraw) {
1060
- // default values for textAlign & textBaseline
1061
- instance.textalign('start', 'top')
1062
-
1077
+ instance.textalign('start', 'top') // default values for textAlign & textBaseline
1063
1078
  instance.emit('draw')
1064
-
1065
- _drawCount++
1066
-
1067
- if (now + _accumulated > _nextFpsUpdate) {
1068
- instance.setvar('FPS', _drawCount)
1069
- _drawCount = 0
1070
- _nextFpsUpdate = now + 1000
1071
- }
1072
1079
  }
1073
1080
 
1074
1081
  _lastFrame = now
@@ -1094,36 +1101,34 @@ export default function litecanvas(settings = {}) {
1094
1101
  _fullscreen = false
1095
1102
  }
1096
1103
 
1104
+ _canvas.style = ''
1097
1105
  _canvas.width = instance.WIDTH
1098
1106
  _canvas.height = instance.HEIGHT || instance.WIDTH
1099
1107
 
1100
1108
  if (!_canvas.parentNode) document.body.appendChild(_canvas)
1101
-
1102
- // canvas CSS tweaks
1103
- _canvas.style.display = 'block'
1104
- if (_fullscreen) {
1105
- _canvas.style.position = 'absolute'
1106
- _canvas.style.inset = 0
1107
- } else if (_autoscale) {
1108
- _canvas.style.margin = 'auto'
1109
- }
1110
1109
  }
1111
1110
 
1112
1111
  function pageResized() {
1113
1112
  const pageWidth = root.innerWidth,
1114
- pageHeight = root.innerHeight
1113
+ pageHeight = root.innerHeight,
1114
+ styles = _canvas.style
1115
+
1116
+ styles.display = 'block'
1115
1117
 
1116
1118
  if (_fullscreen) {
1119
+ styles.position = 'absolute'
1120
+ styles.inset = 0
1117
1121
  instance.setvar('WIDTH', (_canvas.width = pageWidth))
1118
1122
  instance.setvar('HEIGHT', (_canvas.height = pageHeight))
1119
1123
  } else if (_autoscale) {
1124
+ styles.margin = 'auto'
1120
1125
  _scale = Math.min(
1121
1126
  pageWidth / instance.WIDTH,
1122
1127
  pageHeight / instance.HEIGHT
1123
1128
  )
1124
1129
  _scale = (settings.pixelart ? ~~_scale : _scale) || 1
1125
- _canvas.style.width = instance.WIDTH * _scale + 'px'
1126
- _canvas.style.height = instance.HEIGHT * _scale + 'px'
1130
+ styles.width = instance.WIDTH * _scale + 'px'
1131
+ styles.height = instance.HEIGHT * _scale + 'px'
1127
1132
  }
1128
1133
 
1129
1134
  instance.setvar('CENTERX', instance.WIDTH / 2)
@@ -1161,7 +1166,7 @@ export default function litecanvas(settings = {}) {
1161
1166
  }
1162
1167
  }
1163
1168
 
1164
- if (settings.global) {
1169
+ if (_global) {
1165
1170
  if (root.__litecanvas) {
1166
1171
  throw 'global litecanvas already instantiated'
1167
1172
  }
package/src/zzfx.js CHANGED
@@ -80,12 +80,12 @@ export const zzfx = (
80
80
  (a < e
81
81
  ? a / e
82
82
  : a < e + m
83
- ? 1 - ((a - e) / m) * (1 - w)
84
- : a < e + m + r
85
- ? w
86
- : a < h - c
87
- ? ((h - a - c) / t) * w
88
- : 0)),
83
+ ? 1 - ((a - e) / m) * (1 - w)
84
+ : a < e + m + r
85
+ ? w
86
+ : a < h - c
87
+ ? ((h - a - c) / t) * w
88
+ : 0)),
89
89
  (f = c
90
90
  ? f / 2 +
91
91
  (c > a
package/types/index.d.ts CHANGED
@@ -18,8 +18,6 @@ declare global {
18
18
  var CANVAS: HTMLCanvasElement
19
19
  /** the amount of time (in seconds) since the game started */
20
20
  var ELAPSED: number
21
- /** the FPS meter */
22
- var FPS: number
23
21
  /** the center X of the game screen */
24
22
  var CENTERX: number
25
23
  /** the center Y of the game screen */
@@ -205,6 +203,14 @@ declare global {
205
203
  * @returns the random number
206
204
  */
207
205
  function randi(min?: number, max?: number): number
206
+ /**
207
+ * If a value is passed, initializes the random number generator with an explicit seed value.
208
+ * Otherwise, returns the current seed state.
209
+ *
210
+ * @param [value]
211
+ * @returns the seed state
212
+ */
213
+ function seed(value?: number): number
208
214
 
209
215
  /** BASIC GRAPHICS API */
210
216
  /**
package/types/types.d.ts CHANGED
@@ -7,8 +7,6 @@ type LitecanvasInstance = {
7
7
  CANVAS: HTMLCanvasElement
8
8
  /** the amount of time (in seconds) since the game started */
9
9
  ELAPSED: number
10
- /** the FPS meter */
11
- FPS: number
12
10
  /** the center X of the game screen */
13
11
  CENTERX: number
14
12
  /** the center Y of the game screen */
@@ -194,6 +192,14 @@ type LitecanvasInstance = {
194
192
  * @returns the random number
195
193
  */
196
194
  randi(min?: number, max?: number): number
195
+ /**
196
+ * If a value is passed, initializes the random number generator with an explicit seed value.
197
+ * Otherwise, returns the current seed state.
198
+ *
199
+ * @param [value]
200
+ * @returns the seed state
201
+ */
202
+ seed(value?: number): number
197
203
 
198
204
  /** BASIC GRAPHICS API */
199
205
  /**