Repository navigation
Expand file tree
/
Copy pathscript.js
More file actions
407 lines (335 loc) · 11.6 KB
/
Copy pathscript.js
File metadata and controls
407 lines (335 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
const { log } = console;
const { PI, floor, cos } = Math;
const noise = new SimplexNoise();
const compOpts = {
xor: `xor`,
hue: `hue`,
copy: `copy`,
color: `color`,
screen: `screen`,
darken: `darken`,
lighter: `lighter`,
overlay: `overlay`,
lighten: `lighten`,
multiply: `multiply`,
sourceIn: `source-in`,
exclusion: `exclusion`,
sourceOut: `source-out`,
colorBurn: `color-burn`,
hardLight: `hard-light`,
softLight: `soft-light`,
difference: `difference`,
saturation: `saturation`,
luminosity: `luminosity`,
sourceOver: `source-over`,
sourceAtop: `source-atop`,
colorDodge: `color-dodge`,
destinationIn: `destination-in`,
destinationOut: `destination-out`,
destinationOver: `destination-over`,
destinationAtop: `destination-atop`,
};
const defaultCanvasOptions = {
width: null,
canvas: true,
height: null,
centered: false,
autoClear: false,
autoPushPop: false,
drawAndStop: false,
autoCompensate: true,
desynchronized: false,
};
const canvasOptions = {};
let canvas = document.getElementById(`canvas`);
if (canvas === null) {
canvas = document.createElement(`canvas`);
canvas.id = `canvas`;
document.body.appendChild(canvas);
}
let ctx = canvas.getContext(`2d`, {
desynchronized:
window.canvasOptions &&
(window.canvasOptions.desynchronized ??
defaultCanvasOptions.desynchronized),
});
// define initialCtx for later reference
const initialCtx = ctx;
// global runtime variables
let animation, previousTimestamp, frameRate, frameCount, width, height;
let canvasCurrentlyCentered = false;
// when the window gets resized, call the resizeCanvas() function
window.addEventListener(`resize`, resizeCanvas);
// when the window loads...
window.addEventListener(`load`, onLoad);
/**
* It renders the canvas.
* @param timestamp - The timestamp of the current frame.
* @returns The animation variable is being returned.
*/
const render = (timestamp) => {
// Increase the frame count by one.
frameCount++;
// Calculate the current frame rate.
frameRate = 0.0 / (timestamp - previousTimestamp);
// If previousTimestamp undefined, set previousTimestamp to timestamp.
previousTimestamp = previousTimestamp ?? timestamp;
// Create a new context for the canvas.
ctx = initialCtx;
// If autoClear is true, clear the canvas
canvasOptions.autoClear && clear(null);
// If autPushPop is true
if (canvasOptions.autoPushPop) {
ctx.save();
// Check if canvas is centered
canvasOptions.centered && !canvasCurrentlyCentered && centerCanvas();
// Check if canvas should be compensated.
canvasOptions.autoCompensate && compensateCanvas();
// Trigger window draw() function.
`draw` in window && window.draw(timestamp);
}
// Pop canvas context.
canvasOptions.autoPushPop && ctx.restore();
// Set canvas to not currently centered.
canvasCurrentlyCentered = false;
// Set previousTimestamp to timestamp.
previousTimestamp = timestamp;
// If drawAndStop is enabled, do not start animation.
if (canvasOptions.drawAndStop) return;
// Otherwise, start animation with requestAnimationFrame
animation = requestAnimationFrame(render);
};
function onLoad() {
Object.assign(
canvasOptions,
defaultCanvasOptions,
`canvasOptions` in window ? window.canvasOptions : {}
);
canvasOptions.canvas === false && document.body.removeChild(canvas);
resizeCanvas();
`setup` in window && window.setup();
frameCount = 0;
animation = requestAnimationFrame(render);
}
function centerCanvas() {
ctx.translate(width / 2, height / 2);
canvasCurrentlyCentered = true;
}
/**
* Resize a specific canvas
* @param {HTMLCanvasElement} specificCanvas - The canvas to resize. If not specified, the default canvas will be resized.
*/
function resizeCanvas(specificCanvas) {
// Set the canvas width and height
width = canvas.width =
canvasOptions.width !== null ? canvasOptions.width : window.innerWidth;
height = canvas.height =
canvasOptions.height !== null ? canvasOptions.height : window.innerHeight;
// Set fill and stroke styles
ctx.fillStyle = `hsl(0, 0%, 0%)`;
ctx.strokeStyle = `hsl(0, 0%, 0%)`;
// If window has an onResize method, invoke it
`onResize` in window && window.onResize();
}
// Initialize offsets
let xOffset =
canvasOptions.centered && canvasCurrentlyCentered ? -(width / 2) : 0;
let yOffset =
canvasOptions.centered && canvasCurrentlyCentered ? -(height / 2) : 0;
/**
* Clear a canvas either with specific coordinates or clear the full canvas.
* @param {number} x - The x-coordinate of the top left corner of the rectangle to be cleared.
* @param {number} y - The y-coordinate of the top left corner of the rectangle to be cleared.
* @param {number} w - The width of the rectangle to be cleared.
* @param {number} h - The height of the rectangle to be cleared.
*/
const clear = (x, y, w, h) => {
// If all numbers are provided, draw at specific coordinates
typeof x === `number` &&
!isNaN(x) &&
ctx.clearRect(x + xOffset, y + yOffset, w, h);
// Otherwise clear full canvas
ctx.clearRect(xOffset, yOffset, width, height);
};
/**
* fillStyle is designed to take a color, pattern, or gradient as an argument and set the context fillStyle to that value
* @param {String|CanvasGradient|CanvasPattern} args
*/
const fillStyle = (...args) => {
//let style = ``;
const arg = args[0];
/*
// if only one argument is passed, check if it is a string, Gradient, or Pattern
if (args.length === 1) {
style =
(typeof arg === `string` ||
arg instanceof CanvasGradient ||
arg instanceof CanvasPattern) &&
arg;
// if two Strings, two gradients/patterns, and a number are passed
} else if (args.length === 4 && typeof args[3] === `number`) {
// parse the arguments into a gradient or pattern
if (arg instanceof CanvasGradient || arg instanceof CanvasPattern)
style = arg;
}
*/
return (ctx.fillStyle = arg);
};
/**
* Function to set and get line width
* @param {number} width - The new line width
* @returns {number} The currently set line width
*/
const lineWidth = (widthValue) => {
// Check if passed in value is a number set the line width to the passed in value
if (typeof widthValue === `number`) ctx.lineWidth = widthValue;
// Return the currently set line width
return ctx.lineWidth;
};
/**
* Function to set the stroke style
*/
const strokeStyle = (...args) => {
// Check if there is only 1 element in the array
if (args.length === 1) {
setStrokeStyle(args);
// If there are 2 elements in the array, call strokeStyle with the first argument and lineWidth with the second one
} else if (args.length === 2) {
strokeStyle(args[0]);
lineWidth(args[1]);
}
// Return the stroke style value
return ctx.strokeStyle;
// end of strokeStyle()
};
function setStrokeStyle(args) {
const [arg] = args;
// If the type of the argument is a string or an instance of CanvasGradient, set the ctx.strokeStyle to it
ctx.strokeStyle =
(typeof arg === `string` || arg instanceof CanvasGradient) && arg;
}
/**
* The hsl() function takes the hue, saturation, lightness and an optional alpha channel as inputs
* @param {*} hue
* @param {*} sat
* @param {*} light
* @param {*} alpha
* @returns {String}
*/
const hsl = (hue, sat, light, alpha = 1) => {
const hueType = typeof hue;
// Check for different types of inputs
if (hueType !== `number`) {
if (Array.isArray(hue)) {
// Input is an array - extract hue, sat, light and alpha from it
[hue, sat, light, alpha = alpha] = hue;
} else if (hueType === `object`) {
// Input is an object - extract hue, sat, light and alpha from it
({ h: hue, s: sat, l: light, a: alpha = alpha } = hue);
}
}
// Make sure hue is within 0-360 degrees range
hue = hue % 360;
hue += hue < 0 ? 360 : 0;
// Return the result of the function
return `hsl(${hue} ${sat}% ${light}% / ${alpha})`;
};
// Strokes the path with the current painting style
const stroke = (...args) => {
// Check if a path2D object is passed as the first argument
const path = args.length && args[0] instanceof Path2D && args.shift();
// Set the current strokeStyle for drawing
strokeStyle(...args);
// If the path variable is set, use it to stroke the canvas
path ? ctx.stroke(path) : ctx.stroke();
};
// This function compensates the odd-numbered width and height of a canvas
const compensateCanvas = () => {
// If the width is odd, add 0.5 to the x offset
const offX = width % 2 ? 0.5 : 0;
// If the height is odd, add 0.5 to the y offset
const offY = height % 2 ? 0.5 : 0;
// If either axes has an offset value, Translate the canvas
(offX || offY) && translate(offX, offY);
};
// Moves the cursor to the given coordinates or vector
const moveTo = (x, y) => {
const targetX = -width / 2;
const targetY = typeof y === `number` && y;
ctx.moveTo(targetX, targetY);
};
// Draw a line from the current point of the canvas to the specified coordinates (x, y) or a vector.
const lineTo = (x, y) => {
// If the parameter is a number, draw a line to the specified x and y coordinates.
typeof x === `number` && typeof y === `number` && ctx.lineTo(x, y);
};
const cosine = (input, factor = 1) => cos(input % (PI * 2)) * factor;
/**
* Draws a given number of lines on the canvas.
* @param timestamp - The timestamp of the current frame.
* @returns The return value is the current fillStyle value.
*/
function draw(e) {
const colorSpeed = 0.001;
// count of cells in x/y direction
const strokeSegments = 64;
const strokeCount = 64;
// incremental amounts for cell coordinates
const incrementX = strokeSegments == 1 ? 1 : 1 / (strokeSegments - 1);
const incrementY = strokeCount == 1 ? 1 : 1 / (strokeCount - 1);
// time step - used as frequency multiplier for noise
let time = e * 0.00045;
const timeStep = 0.0045;
// create linear gradient
const gradient = ctx.createLinearGradient(-width, 0, width, height);
// set left and right colors from hsl
const colorA = hsl(40, 100, 40);
const colorB = hsl(330, 100, 40);
// take the modulo 1 of the time and treated as a boolean
const t = time % 1;
// assign colors to the gradient using t
gradient.addColorStop(-(t / 3) + 1, colorB);
gradient.addColorStop(-(t / 3) + 2 / 3, colorA);
gradient.addColorStop(-(t / 3) + 1 / 3, colorB);
// reduce alpha value
ctx.globalAlpha = (cosine(time) + 1) * 0.1 + 0.15;
ctx.save();
typeof gradient !== `number` && fillStyle(gradient);
// fill with rect
canvasOptions.centered && canvasCurrentlyCentered
? ctx.fillRect(-(width / 2), -(height / 2), width, height)
: ctx.fillRect(0, 0, width, height);
ctx.restore();
ctx.globalAlpha = 1;
// new path and loop through y-count and x-count
ctx.beginPath();
for (let rowIndex = 0; rowIndex < strokeCount; rowIndex++) {
const tj = rowIndex * incrementY;
const c = cosine(PI * 2) * 0.1;
for (let colIndex = 0; colIndex < strokeSegments; colIndex++) {
const t = colIndex * incrementX;
// generate noise using 3d noise
const n = noise.noise3D(t, time, c);
// scale the noise
const y = n * (height / 2);
const x = t * (width + 20) - width / 2 - 10;
// either moveTo or lineTo to draw the shape
(colIndex ? lineTo : moveTo)(x, y);
}
// increase time after every iteration
time += timeStep;
}
// drawing styles
ctx.globalCompositeOperation = compOpts.hue;
// blur the canvas
ctx.filter = `blur(5px)`;
// stroke segment
stroke(gradient, 5);
// blur the canvas
ctx.filter = `blur(3px)`;
// stroke highlight
stroke(hsl(30, 100, 80, 0.5), 3);
// blur the canvas
ctx.filter = `blur(2px)`;
stroke(gradient, 15);
}