q5 1.9.21 → 1.9.46

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/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # <img src="q5js_logo.webp" height="64"> <img src="q5js_brand.webp" height="64">
2
2
 
3
- **q5.js** implements all of [p5][]'s 2D drawing, math, and user input functionality.
3
+ The sequel to p5.js is here!
4
4
 
5
- It's a drop-in replacement that's performance optimized and 23x smaller than p5. q5 even has a few exclusive features: top-level global mode, HDR color support, namespace mode, and text image caching.
5
+ **q5.js** implements all of [p5][]'s 2D drawing, math, and user input functionality.
6
6
 
7
- But q5 doesn't include any friendly error messages, so its mainly for people who are already familiar with p5.js or JS programming in general. If you're a beginner, stick with p5 while developing a sketch, then use q5 to share your work.
7
+ It's a drop-in replacement that's performance optimized and ~20x smaller than p5, while packing exclusive new features: HDR color support, modular use, top-level global mode, namespace mode, and text image caching.
8
8
 
9
- ## Usage
9
+ ## Typical Use
10
10
 
11
11
  q5 should work with your existing p5.js sketches, no modifications required! If you have any problems though, please [make an issue report][].
12
12
 
@@ -37,22 +37,53 @@ q5.js is compatible with popular p5 addons and projects that use p5, such as [p5
37
37
  To use addons, simply load them after q5.js:
38
38
 
39
39
  ```html
40
- <script src="q5.js"></script>
40
+ <script src="https://q5js.org/q5.js"></script>
41
41
  <!-- load p5 addons after q5 -->
42
42
  <script src="https://p5play.org/v3/planck.min.js"></script>
43
43
  <script src="https://p5play.org/v3/p5play.js"></script>
44
44
  ```
45
45
 
46
- ## New Features: Top-Level Global Mode
46
+ ## Exclusive Features
47
+
48
+ q5 includes some exclusive features that aren't available in p5.
49
+
50
+ ## Ask AI ✨
51
+
52
+ Why doesn't this code work? `text('Hello!');`
53
+
54
+ JavaScript quietly avoids errors if possible (for example by giving undefined variables default values) and its error messages can be confusing for beginners.
55
+
56
+ p5's error messages are friendlier but often too vague, leaving beginners searching for help. 🙋
57
+
58
+ ```
59
+ 🌸 p5.js says: [test.js, line 19] text() was expecting at least 3 arguments, but received only 1.
60
+ ```
61
+
62
+ q5 creates error reports that can be sent to an AI just by clicking a link! Users can also run the `askAI()` function before a line of code that isn't working as expected. 🤖
63
+
64
+ ```js
65
+ function draw() {
66
+ askAI();
67
+ text('Hello!');
68
+ }
69
+ ```
47
70
 
48
- > q5.js includes some exclusive features that aren't available in p5.js. Using them is optional!
71
+ Optionally `askAI` can take a question as input, the default question is "What's wrong with this line? short answer".
72
+
73
+ ChatGPT 4o excels at identifying the most common errors that beginners make: typos, missing syntax, incorrect arguments, and more.
74
+
75
+ This feature can be disabled by setting `Q5.disableFriendlyErrors = true;`, though unlike in p5 this doesn't provide a performance boost from disabling argument validation because q5 mostly doesn't have any already.
76
+
77
+ q5 can catch errors in q5 function like `draw` and continue looping if you set `Q5.errorTolerant = true;`.
78
+
79
+ ## Top-Level Global Mode
49
80
 
50
81
  In **p5**, functions like `rect` can't be used on the file level. They must be called from within p5 functions like `setup` and `draw`.
51
82
 
52
83
  In **q5**, existing p5 2D sketches don't require any modification. But if you initialize Q5 at the top of your sketch, the `preload` and `setup` functions become optional.
53
84
 
54
85
  ```js
55
- new Q5();
86
+ Q5();
56
87
 
57
88
  noStroke();
58
89
  let c = color(0, 126, 255, 102);
@@ -63,7 +94,7 @@ rect(15, 15, 35, 70);
63
94
  This is great because you don't have to declare variables on the file level and then define them in `preload` or `setup`. You can declare and define them at the same time!
64
95
 
65
96
  ```js
66
- new Q5();
97
+ Q5();
67
98
 
68
99
  let cow = loadImage('cow.png');
69
100
 
@@ -76,7 +107,7 @@ function setup() {
76
107
 
77
108
  Note that if you use `loadImage` on the file level, q5 will wait to run `setup` and `draw` until the image loads. Optionally if you forgo defining `preload`, you can run it to signify that the sketch can start once loading is complete. Otherwise q5 will auto-start the sketch after 32ms of delay, this ensures code after `new Q5()` is run before the sketch starts.
78
109
 
79
- ## New Features: HDR Color Support
110
+ ## HDR Color Support
80
111
 
81
112
  Most modern devices support the "display-p3" HDR color space. If a device doesn't support it, q5 will fall back to "srgb".
82
113
 
@@ -103,7 +134,7 @@ Support for the HSV color format was removed in q5 v1.9.3 because color experts
103
134
 
104
135
  https://en.wikipedia.org/wiki/HSL_and_HSV#Disadvantages
105
136
 
106
- ## New Features: Customize Canvas Context Attributes
137
+ ## Customize Canvas Context Attributes
107
138
 
108
139
  In **p5**, you're stuck with the default [canvas context attributes][], which can't be changed. So the canvas must have an alpha layer, even if you don't need one. HDR color space and [desynchronized rendering][] are not supported.
109
140
 
@@ -127,7 +158,7 @@ createCanvas(400, 400, '2d', {
127
158
  });
128
159
  ```
129
160
 
130
- ## New Features: Namespace Mode
161
+ ## Namespace Mode
131
162
 
132
163
  **p5**'s [instance mode][] enables multiple sketches to run on one page. To avoid needing to preface every p5 function with `p.` you can use a JS [with statement][].
133
164
 
@@ -177,11 +208,17 @@ q5 will automatically load and configure `canvas` and `jsdom` if they are instal
177
208
 
178
209
  In node.js, q5's automatic global mode is disabled. To use global mode you need to assign q5 user defined functions like `draw` and `setup` to the `global` object then call `new Q5()`. q5 will add q5 variables and functions to the `global` object, just like it adds them to the `window` object in the browser.
179
210
 
211
+ ## Modular Use
212
+
213
+ **p5.js** is nearly 5MB in size. This is mainly [due to the inclusion of the webgl render and the dependencies corejs and opentype](https://github.com/processing/p5.js/issues/6776#issuecomment-1918238317). If 2d rendering is all a sketch needs, p5 wastes user bandwidth and is slower to load, parse, and run.
214
+
215
+ **q5.js** (the default bundle) is 20x smaller than p5, which is already great for typical use. For extremely lightweight use you can load a subset of scripts from the `src` folder, just be sure to load `src/q5-core.js` first.
216
+
180
217
  ## Motivation: Part 1
181
218
 
182
- > This section was written by @LingDong-, co-creator of q5.
219
+ > This section was written by @LingDong-
183
220
 
184
- After having used many graphics libraries across many different languages, I have found that the Processing/p5.js/Openframeworks system has one huge advantage over others:
221
+ After having used many graphics libraries across many different languages, I have found that the Processing system has one huge advantage over others:
185
222
 
186
223
  It gets stuff drawn onto the screen quick and easy!
187
224
 
@@ -195,7 +232,7 @@ In fact, its not uncommon for successful software systems to have multiple imple
195
232
 
196
233
  ## Motivation: Part 2
197
234
 
198
- > This section was written by @quinton-ashley, co-creator of q5.
235
+ > This section was written by @quinton-ashley
199
236
 
200
237
  I thought @LingDong-'s work on q5 and the idea itself had great potential, so I decided to implement more of the p5.js API. My main goal was to make it work with [p5play](https://p5play.org)!
201
238
 
@@ -203,14 +240,19 @@ An increase in performance of even a few frames per second can make a significan
203
240
 
204
241
  I was also interested in working on q5 because for a lot of p5.js users, the library itself is a black box. Even as an expert JS programmer and someone who teaches CS for a living, I still find myself scratching my head when I look at the p5.js source code. p5 was initially released 10 years ago and bad design choices were made due to JS limitations at the time. It's also become an absolutely massive library, with literally over 100,000 lines of code and documentation!
205
242
 
206
- I think it'd be better if the canvas mode, webgl mode, Friendly Error System, and accessibility features of p5 were offered in separate files. Yet, the powers that be at the Processing Foundation have made it clear that they don't want to do that. Instead they insist on adding more accessibility features to the base library, which the majority of people just don't need. So q5 is a good alternative that trims out the fat.
243
+ I think it'd be better if the canvas mode, webgl mode, Friendly Error System, and accessibility features of p5 were offered in separate files. Yet, the powers that be at the Processing Foundation have made it clear that they don't want to do that. So q5 is a good alternative that trims out the fat.
207
244
 
208
245
  Thanks in large part to @LingDong-'s design, q5 is well organized, concise, and utilizes many modern JS features! I think even without inline documentation, the source code is easier for experienced JS programmers to comprehend.
209
246
 
210
247
  ## More exclusive features
211
248
 
249
+ Features added by @Tezumie:
250
+
251
+ - `point()`: more efficient point drawing.
252
+
212
253
  Features added by @quinton-ashley:
213
254
 
255
+ - `image.trim()`: removes transparent pixels from the edges of an image.
214
256
  - `opacity(globalAlpha)`: set the opacity multiplier for anything subsequently drawn to the canvas in a range between 0 (transparent) and 1 (opaque).
215
257
  - `textCache(enabled)`: Text image caching is enabled by default. Rotated text is only rendered once, and then cached as an image. This can result in ridiculously high 90x performance boosts for text-heavy sketches. Users don't need to change their code, the `text` function can be used as normal, q5 takes care of everything behind the scenes.
216
258
  - `createImage`, `loadImage`, and `createGraphics`: as a last parameter to these functions, `opt` (options) object, users can specify canvas context attributes for an image or graphic. `opt.alpha` is set to true by default.
@@ -225,21 +267,21 @@ Features added by @LingDong-:
225
267
 
226
268
  ## Limitations
227
269
 
228
- - `color` function only accepts numeric input, hex, and simple named colors. It doesn't parse strings like `color('hsl(160, 100%, 50%)')`. This was done to keep the codebase small and easier to understand.
270
+ - `color` function only accepts numeric input, hex, and simple named colors. It doesn't parse strings like `color('hsl(160, 100%, 50%)')`. q5 supports oklch which is a superior color format so use it instead. The `fill`, `stroke`, and `background` functions can accept any css color string though.
229
271
 
230
272
  ## Size Comparison
231
273
 
232
274
  Unminified:
233
275
 
234
- - p5.js **4300kb** ⚠️
276
+ - p5.js **4958kb** ⚠️
235
277
  - p5.sound.js 488kb
236
- - q5.js 66kb
278
+ - q5.js 71kb
237
279
 
238
280
  Minified:
239
281
 
240
- - p5.min.js 1000kb
282
+ - p5.min.js 1033kb ⚠️
241
283
  - p5.sound.min.js 200kb
242
- - q5.min.js **42kb** 🎉
284
+ - q5.min.js **45kb** 🎉
243
285
 
244
286
  ## Benchmarks
245
287
 
@@ -278,17 +320,29 @@ Higher FPS (frames per second) is better.
278
320
 
279
321
  <sub>\* Only for browsers that support CanvasRenderingContext2D.filter ([75% of all](https://caniuse.com/#feat=mdn-api_canvasrenderingcontext2d_filter) as of Aug 2020, including Chrome, Firefox and Edge). For those that don't, performance is similar to p5.js, as identical implementations are usually used as fallbacks.</sub>
280
322
 
281
- ## Contributing
323
+ ## Contributor Code of Conduct
324
+
325
+ We aim to make contributing to the q5 project more approachable for non-experts by using modern JavaScript without any complicated build tools or frameworks.
326
+
327
+ All contributors are required to check their ego at the door and be open to feedback. Critique of code is not a critique of the person who wrote it. We're all here to learn and work with others to collectively write the best code possible.
328
+
329
+ Code is a language art that can be subjectively judged by its effectiveness at communicating its functionality to humans. Code can also be objectively measured by its performance. Since JavaScript is served over a network, size is a factor as well. Therefore, the q5 team strives to balance code readability with brevity and performance.
330
+
331
+ Check out the q5 project planning board:
332
+
333
+ https://github.com/users/quinton-ashley/projects/4/views/1
334
+
335
+ ## Organization
282
336
 
283
- Speed is a goal for q5.js, and we would very much like to see the above list grow. If you know how to make something faster, advice/pull requests are very welcome!
337
+ If the q5 project is successful, all contributing developers will be paid for their work. The project will be run as a [worker co-op](https://en.wikipedia.org/wiki/Worker_cooperative).
284
338
 
285
339
  ## Licensing
286
340
 
287
- q5.js is not affiliated with the Processing Foundation. p5.js is licensed under the LGPLv2, the two small sections of p5.js' code were directly copied into q5.js are credited below. The rest of q5 is a new implementation of part of the p5.js API. APIs are not copyrightable in the United States, as decided by the Supreme Court in the Google v Oracle case.
341
+ q5.js was created by the q5 team and is licensed under the LGPLv3. q5 is not affiliated with The Processing Foundation.
288
342
 
289
- @LingDong- created the original q5xjs library and licensed it under the MIT license.
343
+ @LingDong- created the original q5xjs library which is MIT licensed.
290
344
 
291
- @quinton-ashley created q5.js (this project) which contains many bug fixes, additional p5.js API implementations, and several exclusive features. q5.js is licensed under the LGPLv3.
345
+ p5.js is licensed under the LGPLv2, the two small sections of p5' code directly copied into q5 are credited below. The rest of q5 is a new implementation of part of the p5 API. APIs are not copyrightable in the United States, as decided by the Supreme Court in the Google v Oracle case.
292
346
 
293
347
  ## Credits
294
348
 
package/package.json CHANGED
@@ -1,12 +1,19 @@
1
1
  {
2
- "author": "quinton-ashley",
3
2
  "name": "q5",
4
- "version": "1.9.21",
5
- "description": "An implementation of the p5.js 2D API that's smaller and faster",
3
+ "version": "1.9.46",
4
+ "description": "The sequel to p5.js that's smaller and faster",
5
+ "author": "quinton-ashley",
6
+ "contributors": [
7
+ "Tezumie",
8
+ "LingDong-"
9
+ ],
10
+ "license": "LGPL-3.0",
11
+ "homepage": "https://q5js.org/home",
6
12
  "main": "q5-server.js",
7
13
  "scripts": {
14
+ "bundle": "cat src/q5-core.js src/q5-2d-canvas.js src/q5-2d-drawing.js src/q5-2d-image.js src/q5-2d-text.js src/q5-ai.js src/q5-color.js src/q5-display.js src/q5-input.js src/q5-math.js src/q5-sound.js src/q5-util.js src/q5-vector.js > q5.js",
8
15
  "min": "terser q5.js --compress ecma=2024 --mangle > q5.min.js",
9
- "dist": "cp q5.js ../../web/p5play-web/v3/q5.js && bun min && cp q5.min.js ../../web/p5play-web/v3/q5.min.js",
16
+ "dist": "bun bundle && cp q5.js ../../web/p5play-web/v3/q5.js && bun min && cp q5.min.js ../../web/p5play-web/v3/q5.min.js",
10
17
  "v": "npm version patch --force",
11
18
  "V": "npm version minor --force",
12
19
  "version": "git add -A",
@@ -14,7 +21,10 @@
14
21
  },
15
22
  "repository": {
16
23
  "type": "git",
17
- "url": "git+https://github.com/quinton-ashley/q5.js.git"
24
+ "url": "git+https://github.com/q5js/q5.js.git"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/q5js/q5.js/issues"
18
28
  },
19
29
  "keywords": [
20
30
  "p5",
@@ -25,9 +35,9 @@
25
35
  "q5.js",
26
36
  "q5js"
27
37
  ],
28
- "license": "LGPL-3.0",
29
- "bugs": {
30
- "url": "https://github.com/quinton-ashley/q5.js/issues"
31
- },
32
- "homepage": "https://github.com/quinton-ashley/q5.js#readme"
38
+ "devDependencies": {
39
+ "canvas": "^2.11.2",
40
+ "jsdom": "^24.1.0",
41
+ "@types/p5": "^1.7.6"
42
+ }
33
43
  }
package/q5-server.js CHANGED
@@ -8,7 +8,8 @@ try {
8
8
  global.CairoCanvas ??= require('canvas');
9
9
  global.JSDOM ??= require('jsdom').JSDOM;
10
10
  } catch (e) {
11
- module.exports = require('./q5.js');
11
+ require('./q5.js');
12
+ module.exports = Q5;
12
13
  return;
13
14
  }
14
15
 
@@ -24,7 +25,7 @@ global.window = new JSDOM('', { url: 'http://localhost' }).window;
24
25
  global.Event = window.Event;
25
26
  }
26
27
 
27
- const Q5 = require('./q5.js');
28
+ require('./q5.js');
28
29
 
29
30
  Q5._createNodeJSCanvas = function () {
30
31
  let cairoCanvas = CairoCanvas.createCanvas(...arguments);
package/q5.js CHANGED
@@ -91,7 +91,7 @@ function Q5(scope, parent) {
91
91
  }
92
92
 
93
93
  $.createCanvas = function (width, height, renderer, options) {
94
- if (renderer == 'webgl') throw `webgl renderer is not supported in q5, use '2d'`;
94
+ if (renderer == 'webgl') throw Error(`webgl renderer is not supported in q5, use '2d'`);
95
95
  if (typeof renderer == 'object') options = renderer;
96
96
  $.width = $.canvas.width = width || window.innerWidth;
97
97
  $.height = $.canvas.height = height || window.innerHeight;
@@ -924,6 +924,7 @@ function Q5(scope, parent) {
924
924
  blue: [0, 0, 255],
925
925
  brown: [165, 42, 42],
926
926
  crimson: [220, 20, 60],
927
+ cyan: [0, 255, 255],
927
928
  darkviolet: [148, 0, 211],
928
929
  gold: [255, 215, 0],
929
930
  green: [0, 128, 0],
@@ -1026,6 +1027,7 @@ function Q5(scope, parent) {
1026
1027
  $._doStroke = true;
1027
1028
  $._strokeSet = true;
1028
1029
  if (!c._q5Color && typeof c != 'string') c = $.color(...arguments);
1030
+ else if (basicColors[c]) c = $.color(...basicColors[c]);
1029
1031
  if (c.a <= 0) return ($._doStroke = false);
1030
1032
  ctx.strokeStyle = c.toString();
1031
1033
  };
@@ -1034,6 +1036,7 @@ function Q5(scope, parent) {
1034
1036
  $._doFill = true;
1035
1037
  $._fillSet = true;
1036
1038
  if (!c._q5Color && typeof c != 'string') c = $.color(...arguments);
1039
+ else if (basicColors[c]) c = $.color(...basicColors[c]);
1037
1040
  if (c.a <= 0) return ($._doFill = false);
1038
1041
  ctx.fillStyle = c.toString();
1039
1042
  };
@@ -1059,7 +1062,8 @@ function Q5(scope, parent) {
1059
1062
  if (c._q5) return $.image(c, 0, 0, $.width, $.height);
1060
1063
  ctx.save();
1061
1064
  ctx.resetTransform();
1062
- if (!c._q5color && typeof c != 'string') c = $.color(...arguments);
1065
+ if (!c._q5Color && typeof c != 'string') c = $.color(...arguments);
1066
+ else if (basicColors[c]) c = $.color(...basicColors[c]);
1063
1067
  ctx.fillStyle = c.toString();
1064
1068
  ctx.fillRect(0, 0, $.canvas.width, $.canvas.height);
1065
1069
  ctx.restore();
@@ -2018,11 +2022,7 @@ function Q5(scope, parent) {
2018
2022
  clearBuff();
2019
2023
  firstVertex = true;
2020
2024
  if (ctx) ctx.save();
2021
- try {
2022
- $.draw();
2023
- } catch (e) {
2024
- console.error(e);
2025
- }
2025
+ $.draw();
2026
2026
  for (let m of Q5.prototype._methods.post) m.call($);
2027
2027
  if (ctx) {
2028
2028
  ctx.restore();
@@ -2311,6 +2311,72 @@ function Q5(scope, parent) {
2311
2311
  return $.audioContext.resume();
2312
2312
  };
2313
2313
 
2314
+ // AI
2315
+
2316
+ $.askAI = (q = '') => {
2317
+ throw Error('Ask AI ✨ ' + q);
2318
+ };
2319
+
2320
+ async function aiErrorAssistance(e) {
2321
+ let askAI = e.message.includes('Ask AI ✨');
2322
+ if (!askAI) console.error(e);
2323
+ if (Q5.disableFriendlyErrors) return;
2324
+ if (askAI || !Q5.errorTolerant) noLoop();
2325
+ let stackLines = e.stack.split('\n');
2326
+ if (stackLines.length <= 1) return;
2327
+
2328
+ let idx = 1;
2329
+ let sep = '(';
2330
+ if (navigator.userAgent.indexOf('Chrome') == -1) {
2331
+ idx = 0;
2332
+ sep = '@';
2333
+ }
2334
+ while (stackLines[idx].indexOf('q5.js:') >= 0) idx++;
2335
+
2336
+ let parts = stackLines[idx].split(sep).at(-1);
2337
+ parts = parts.split(':');
2338
+ let lineNum = parseInt(parts.at(-2));
2339
+ if (askAI) lineNum++;
2340
+ let fileUrl = parts.slice(0, -2).join(':');
2341
+ let fileBase = fileUrl.split('/').at(-1);
2342
+
2343
+ try {
2344
+ let res = await (await fetch(fileUrl)).text();
2345
+ let lines = res.split('\n');
2346
+ let errLine = lines[lineNum - 1].trim();
2347
+
2348
+ let context = '';
2349
+ let i = 1;
2350
+ while (context.length < 1600) {
2351
+ if (lineNum - i >= 0) {
2352
+ context = lines[lineNum - i].trim() + '\n' + context;
2353
+ }
2354
+ if (lineNum + i < lines.length) {
2355
+ context += lines[lineNum + i].trim() + '\n';
2356
+ }
2357
+ i++;
2358
+ }
2359
+
2360
+ let question =
2361
+ askAI && e.message.length > 10 ? e.message.slice(10) : 'Whats+wrong+with+this+line%3F+short+answer';
2362
+
2363
+ let url =
2364
+ 'https://chatgpt.com/?q=q5.js+' +
2365
+ question +
2366
+ (askAI ? '' : '%0A%0A' + encodeURIComponent(e.name + ': ' + e.message)) +
2367
+ '%0A%0ALine%3A+' +
2368
+ encodeURIComponent(errLine) +
2369
+ '%0A%0AExcerpt+for+context%3A%0A%0A' +
2370
+ encodeURIComponent(context);
2371
+
2372
+ if (!askAI) console.log('Error in ' + fileBase + ' on line ' + lineNum + ':\n\n' + errLine);
2373
+
2374
+ console.warn('Ask AI ✨ ' + url);
2375
+
2376
+ if (askAI) window.open(url, '_blank');
2377
+ } catch (err) {}
2378
+ }
2379
+
2314
2380
  // INIT
2315
2381
 
2316
2382
  if (scope == 'global') {
@@ -2354,7 +2420,7 @@ function Q5(scope, parent) {
2354
2420
 
2355
2421
  let t = scope == 'global' ? (!Q5._nodejs ? window : global) : $;
2356
2422
  let preloadDefined = t.preload;
2357
- let eventNames = [
2423
+ let userFns = [
2358
2424
  'setup',
2359
2425
  'draw',
2360
2426
  'preload',
@@ -2371,9 +2437,17 @@ function Q5(scope, parent) {
2371
2437
  'touchEnded',
2372
2438
  'windowResized'
2373
2439
  ];
2374
- for (let k of eventNames) {
2440
+ for (let k of userFns) {
2375
2441
  if (!t[k]) $[k] = () => {};
2376
- else if ($._isGlobal) $[k] = t[k];
2442
+ else if ($._isGlobal) {
2443
+ $[k] = () => {
2444
+ try {
2445
+ t[k]();
2446
+ } catch (e) {
2447
+ aiErrorAssistance(e);
2448
+ }
2449
+ };
2450
+ }
2377
2451
  }
2378
2452
 
2379
2453
  $._isTouchAware = $.touchStarted || $.touchMoved || $.mouseReleased;
@@ -2382,7 +2456,10 @@ function Q5(scope, parent) {
2382
2456
  window.addEventListener('mousemove', (e) => $._onmousemove(e), false);
2383
2457
  window.addEventListener('keydown', (e) => $._onkeydown(e), false);
2384
2458
  window.addEventListener('keyup', (e) => $._onkeyup(e), false);
2385
- window.addEventListener('resize', () => ($._shouldResize = true));
2459
+ window.addEventListener('resize', () => {
2460
+ $._shouldResize = true;
2461
+ if (!$._loop) $.redraw();
2462
+ });
2386
2463
  }
2387
2464
 
2388
2465
  if (!($.setup || $.draw)) return;
@@ -2765,7 +2842,7 @@ if (!window.matchMedia || !matchMedia('(dynamic-range: high) and (color-gamut: p
2765
2842
 
2766
2843
  Q5._instanceCount = 0;
2767
2844
  Q5._friendlyError = (msg, func) => {
2768
- throw func + ': ' + msg;
2845
+ throw Error(func + ': ' + msg);
2769
2846
  };
2770
2847
  Q5._validateParameters = () => true;
2771
2848