cdejs
Version:
CanvasDotEffect is a lightweight JS library that helps create customizable and interactive dot-based effects using the Canvas API
1,092 lines (859 loc) • 112 kB
Markdown
[](https://github.com/Louis-CharlesBiron/canvasDotEffect/commits/main/)
[](https://github.com/Louis-CharlesBiron/canvasDotEffect/commits/main/)
[](https://www.npmjs.com/package/cdejs)
[](https://www.npmjs.com/package/cdejs)


# CanvasDotEffect
**CanvasDotEffect is a lightweight, fully native, JS library that helps create customizable and interactive dot-based effects using the Canvas API.**
# Table of Contents
- [Getting Started / Minimal setup](#getting-started--minimal-setup)
- [Classes](#class-descriptions)
- [Canvas](#canvas)
- [_Obj](#_obj)
- [Dot](#dot)
- [Shape](#shape)
- [Filled Shape](#filled-shape)
- [Grid](#grid)
- [Grid Assets](#grid-assets)
- [TextDisplay](#textdisplay)
- [ImageDisplay](#imagedisplay)
- [AudioDisplay](#audiodisplay)
- [Color](#color)
- [Gradient](#gradient)
- [Pattern](#pattern)
- [Render](#render)
- [TextStyles](#textStyles)
- [RenderStyles](#renderstyles)
- [Anim](#anim)
- [Input Devices](#input-devices)
- [TypingDevice](#typingdevice)
- [Mouse](#mouse)
- [Utilities](#utilities)
- [CanvasUtils](#canvasutils)
- [CDEUtils](#cdeutils)
- [FPSCounter](#fpscounter)
- [Npx commands](#npx-commands)
- [React component template](#react-component-template)
- [Execution order](#execution-order)
- [Optimization](#optimization)
- [Intended practices](#intended-practices)
- [Credits](#credits)
## Getting Started / Minimal setup
#### Either run: `npx cdejs template yourProjectName` or follow these couple steps! (↓)
1. **Get the library file. (`npm install cdejs` or [canvasDotEffect.min.js](https://github.com/Louis-CharlesBiron/canvasDotEffect/blob/main/dist/canvasDotEffect.min.js))**
```HTML
<!-- Only if you're using the browser version! Otherwise use: import {...} from "cdejs" -->
<script src="canvasDotEffect.min.js"></script>
```
2. **In your HTML file, place a canvas element. The canvas will later automatically take the size of its parent element**.
```HTML
<div class="canvasHolder">
<canvas id="canvasId"></canvas>
</div>
```
3. **In a JS file, create a new Canvas instance with the HTML canvas element in parameter.**
```js
const CVS = new Canvas(document.getElementById("canvasId"))
```
4. **From this Canvas instance, add any canvas objects you want.**
```js
// Create a canvas object
const dummyShape = new Shape([50, 50], [new Dot()])
// Add it to the canvas
CVS.add(dummyShape)
```
5. **Set the mouse event listeners for mouse interactions.**
```js
// Set up the prebuilt event listeners, allowing the creation of more interactive effects!
CVS.setMouseMove(/*custom callback*/)
CVS.setMouseLeave()
CVS.setMouseDown()
CVS.setMouseUp()
```
6. **Once everything is created and ready to go, start the drawing loop!**
```js
// Start
CVS.start()
```
**- In the end, you should have something like this:**
```js
const CVS = new Canvas(document.getElementById("canvasId"))
// Creating and adding shapes ...
const dummyShape = new Shape([50, 50], [new Dot()])
CVS.add(dummyShape)
CVS.setMouseMove()
CVS.setMouseLeave()
CVS.setMouseDown()
CVS.setMouseUp()
CVS.start()
```
**Note:** if you are using de [NPM](https://www.npmjs.com/package/cdejs) version of this librairy, using [Vite](https://vite.dev/) or any other bundler is recommended.
###### - Minimal example package.json
```json
{
"name": "my_project",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"dependencies": {
"cdejs": "^1.3.0"
},
"devDependencies": {
"vite": "^6.2.2"
}
}
```
# [Class descriptions](#table-of-contents)
The following sections are short documentations of each class, basically what it does and what are the most important aspects of it.
# [Canvas](#table-of-contents)
The Canvas class is the core of the project. It manages the main loop, the window listeners, the delta time, the HTML canvas element, all the canvas objects, and much more.
#### **The Canvas constructor takes the following parameters:**
###### - `new Canvas(cvs, loopingCB, fpsLimit, cvsFrame, settings, willReadFrequently)`
- *id* -> The identifier of the canvas instance.
- **cvs** -> The HTML canvas element to link to.
- **loopingCB**? -> A callback ran each frame. `(Canvas)=>`
- **fpsLimit**? -> The maximum fps cap. Defaults and caps to V-Sync.
- **cvsFrame**? -> If you don't want the canvas to take the size of its direct parent, you can provide another custom HTML element here.
- **settings**? -> The custom canvas settings (leave `null` for prebuilt default settings).
- **willReadFrequently**? -> If `true`, optimizes the canvas context for frequent readings. (Defaults to `false`)
**To add objects to the canvas,** use the add() function:
###### - add(objs, inactive=false)
```js
// For a source object
CVS.add(yourShape)
// For a prefab or inactive shape
CVS.add(yourShape, true)
```
**To set up mouse/keyboard listeners for the canvas,** use the following prebuilt functions:
```js
// Set the important mouse events
CVS.setMouseMove(/*possible custom callback*/)
CVS.setMouseLeave()
CVS.setMouseDown()
CVS.setMouseUp()
// Set the important keyboard events
CVS.setKeyDown(/*possible custom callback*/)
CVS.setKeyUp()
```
**To control the canvas loop**, use the following functions:
```js
// Starts the main loop
CVS.start()
// Stops the main loop
CVS.stop()
```
**To control the canvas drawing loop speed**, use the following function:
```js
// Slows down all animations/deltaTime related events by 4x
CVS.speedModifier = 0.25
```
#### Example use:
###### - Creating a Canvas instance that displays fps
```js
// Creating a FPSCounter instance
const fpsCounter = new FPSCounter()
const CVS = new Canvas(
document.getElementById("canvas"), // The HTML canvas element to link to
()=>{ // Custom callback that runs every frame
// Get current fps
const fps = fpsCounter.getFps()
// Display fps in another element
document.getElementById("fpsDisplay").textContent = fps
}
)
```
# [_Obj](#table-of-contents)
The _Obj class is the template class of most canvas object. **It should not be directly instantiated.**
#### **All canvas objects will have at least these attributes:**
- ***id*** -> Id of the object.
- **initPos** -> Initial pos declaration. Can either be a pos array `[x, y]` or a callback `(Canvas, obj)=>{... return [x, y]}`
- ***pos*** -> Array containing the `[x, y]` position of the object.
- ***initRadius*** -> Initial radius declaration. Can either be a number or a callback `(parent or obj)=>{... return radiusValue}`
- **radius** -> The radius in px object the dot (Or the radius of its dots if is a Shape).
- ***initColor*** -> Initial color declaration. Can either be a color value (see ↓) or a callback `(render, obj)=>{... return colorValue}`
- **color** -> Either a Color instance `new Color("red")`, a string `"red"`, a hex value `#FF0000` or a RGBA array `[255, 0, 0, 1]`
- **setupCB** -> Custom callback called on the object's initialization `(this, parent?)=>{}`s
- ***setupResults*** -> The value returned by the `setupCB` call.
- **loopCB** -> Custom callback called each frame for the object (obj, deltaTime)=>
- **anchorPos** -> The reference point from which the object's pos will be set. Can either be a pos `[x,y]`, another canvas object instance, or a callback `(obj, Canvas or parent)=>{... return [x,y]}` (Defaults to the parent's pos, or `[0, 0]` if the object has no parent). If your *anchorPos* references another object, make sure it is defined and initialized when used as the *anchorPos* value.
- **activationMargin** -> Defines the px margin where the object remains active when outside the canvas' visible bounds. If `true`, the object will always remain active.
- ***initialized*** -> Whether the object has been initialized.
- ***parent*** -> The parent of the object. (Shape, Canvas, ...)
- ***rotation*** -> The object's rotation in degrees. Use the `rotateAt`, `rotateBy` and `rotateTo` functions to modify.
- ***scale*** -> The shape's X and Y scale factors `[scaleX, scaleY]`. Use the `scaleAt`, `scaleBy` and `scaleTo` functions to modify.
- ***visualEffects*** -> The visual effects of the object in an array: `[filter, compositeOperation, opacity]`. `filter` is a string containing a regular css filter (`"blur(5px)"`, `url(#svgFilterId)`, etc). `compositeOperation` is one of `Render.COMPOSITE_OPERATIONS` (see [global composite operations](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation) for more information). `opacity` is the alpha value of the object (in addition to the object's color alpha).
**This class also defines other useful base functions**, such as:
- Movements functions (`moveBy`, `addForce`, `follow`, ...)
- Informative functions (`isWithin`, `getInitPos`, `getBounds`, ...)
- Access to the object's animations (`playAnim`, `clearAnims`, ...)
- Visibility functions (`enable`, `disable`, ...)
**The follow function:** use `follow()` to make an object follow a custom path:
###### - `follow(duration, easing, action, progressSeparations)`
```js
/**
* Used to make an object follow a custom path
* @param {Number} duration: The duration of the animation in miliseconds
* @param {Function} easing: The easing function
* @param {Function?} action: A custom callback that can be called in addition to the movement //newProg is 'prog' - the progress delimiter of the range
* @param {[[Number, Function], ...]} progressSeparations: list of callback paired with a progress range, the callback must return a position (prog, newProg, initX, initY)=>return [x,y]
* progressSeparations example: [0:(prog)=>[x1, y1]], [0.5:(prog, newProg)=>[x2, y2]] -> from 0% to 49% the pos from 1st callback is applied, from 50%-100% the pos from 2nd callback is applied
*/
// Example use, for 3 seconds, easeOutQuad, no custom callback, will travel in a sideways 'L' shape
let dx=400, dy=200
dot.follow(3000, Anim.easeOutQuad, null, [[0,(prog)=>[dx*prog, 0]], [0.5,(prog, newProg)=>[dx*0.5, dy*newProg]]])
```
# [Dot](#table-of-contents)
The dot class is **meant** to be the *core* of most effects. It appears as a circular dot on the canvas by default.
#### **The Dot constructor takes the following parameters:**
###### - `new Dot(pos, radius, color, setupCB, anchorPos, activationMargin, disablePathCaching)`
- *pos, radius, color, setupCB, anchorPos, activationMargin* -> See the _Obj class.
Its other attribute is:
- **connections** -> a list referencing other dots, primarily to draw a connection between them.
- **cachedPath** -> The cached Path2D object or `null` if path caching is disabled (Controlled via the `disablePathCaching` constructor parameter or by the `disablePathCaching()` function)
**To add or remove connections,** use the following functions:
```js
// Adding a connection with another dot
dot.addConnection(otherDot)
// Removing a connection
dot.removeConnection(otherDot)
```
**To control whether a dot caches its path** use the following functions:
```js
// By default, path caching for dots is enabled.
// But for very dynamic dots (changes every frame), sometimes it might be better to disable caching.
// To disable path caching:
const dynamicDot = new Dot(null, null, null, null, null, null, true) // disables the path caching via constructor
existingDynamicDot.disablePathCaching() // disables the path caching via this function if the dot already exists
// To enable path caching back use:
existingDynamicDot.updateCachedPath()
```
**To delete a dot**, use the following function:
```js
// Removes the dot completely
dot.remove()
```
#### Example use 1:
###### - A dot on its own (rare)
```js
// Creating a lonely dot
const aloneDot = new Dot(
[0,0], // positioned at [0,0]
25, // 25px radius
[0,0,255,0.5], // blue at 50% opacity
()=>{ // setupCB, custom callback ran on dot's initialization
console.log("I am now added to the canvas and ready to go!")
}
)
// Add the dot as a standalone object (definition)
CVS.add(aloneDot)
```
#### Example use 2:
###### - Dots as part of a shape
```js
// Creating a Shape containing some dots!
const squareLikeFormation = new Shape([0,0], [
new Dot([100, 100]), // Dots contained in a shape will take on some properties (color, radius, limit, ...) of the shape.
new Dot([150, 100]),
new Dot([150, 150]),
new Dot([100, 150])
], _Obj.DEFAULT_RADIUS, Color.DEFAULT_COLOR, Shape.DEFAULT_LIMIT)
// Add the shape along with all of its dots as a single unit. (reference)
CVS.add(squareLikeFormation)
```
# [Shape](#table-of-contents)
The Shape class (or its inheritors) plays a crucial part in creating proper effects. It provides the needed control over a group of dots and is used to make pretty much any effect. An empty shape (containing no dots) on its own is not visible by default.
One of the main features is the ***drawEffectCB***. This callback allows the creation of custom effects for each dot in the shape.
Effects are often ratio-based, meaning the *intensity* of the effect is based on the distance between the dot and the *ratioPos*. You can control the affected distance with the *limit* parameter, and the the object to which the distance\ratio is calculated with the *ratioPosCB* parameter.
#### **The Shape constructor takes the following parameters:**
###### - `new Shape(pos, dots, radius, color, limit, drawEffectCB, ratioPosCB, setupCB, loopCB, anchorPos, activationMargin, fragile)`
- *pos, radius, color, setupCB, loopCB, anchorPos, activationMargin* -> See the _Obj class.
- **initDots** -> Initial dots declaration. Can either be: an array of dots `[new Dot(...), existingDot, ...]`, a **String** (this will automatically call the shape's createFromString() function), or a callback `(Shape, Canvas)=>{... return anArrayOfDots}`
- ***dots*** -> Array of all the current dots contained by the shape.
- **limit** -> Defines the circular radius in which the dots' ratio is calculated. Each dot will have itself as its center to calculate the distance between it and the shape's *ratioPos*. (At the edges the ratio will be 0 and gradually gravitates to 1 at the center)
- **drawEffectCB** -> A custom callback containing the effects to display. It is run by every dot of the shape, every frame. `(render, dot, ratio, parentSetupResults, mouse, distance, parent, isActive, rawRatio)=>{...}`.
- **ratioPosCB**? -> References the mouse position by default. Can be used to set a custom *ratioPos* target `(Shape, dots)=>{... return [x, y]}`. Can be disabled if set to `null`.
- **fragile**? -> ***/!\\ DEPRACATED /!\\*** Whether the shape resets on document visibility change events. (Rarer, some continuous effects can break when the page is in the background due to the unusual deltaTime values sometimes occurring when the document is offscreen/unfocused)
### **To add one or many dots,** use the add() function:
###### - add(dots)
```js
// Creating and adding a new empty Shape to the canvas
const dummyShape = new Shape([100,100])
CVS.add(dummyShape)
// Later, adding a dot to the shape, at [150, 150] on the canvas
dummyShape.add(new Dot(50, 50))
// or many dots
dummyShape.add([new Dot(50, 50), new Dot(50, 100)])
```
**Note, adding dots to a shape:**
1. Sets the dots' the dots' color to the one of shape, if not previously defined.
2. Sets the dots' radius to the one of the shape, if not previously defined.
3. Sets the dots' anchorPos to the shape's pos, if not previously defined.
4. Sets the dots' activationMargin property to that of the shape, if not previously defined.
5. Sets the dots' parent attribute to reference the shape.
### **To modify dots' properties all at once,** use the following functions:
###### - setRadius(radius, onlyReplaceDefaults), setColor(color, onlyReplaceDefaults), setLimit(limit, onlyReplaceDefaults), [enable/disable]DotsPathCaching()
```js
// Sets the radius of all dummyShape's dots to 10
dummyShape.setRadius(10)
// Sets the color of all dummyShape's dots to red
dummyShape.setColor("red")
// Sets the limit of all dummyShape's dots to 100
dummyShape.setLimit(100)
// Disables the path caching of all dummyShape's dots
dummyShape.disableDotsPathCaching()
// Enables the path caching of all dummyShape's dots
dummyShape.enableDotsPathCaching()
```
### **To dynamically generate a formation of dots** use the `generate` functions:
###### - generate(yTrajectory, startOffset, length, gapX, yModifier, generationCallback)
```js
// Generating a sine wave-based formation
const generatedDots = Shape.generate(
x=>Math.sin(x/50)*100, // make the y follow a sine wave pattern
[-50, 0], // the generation start is offset by -50 horizontally
1000, // the generation will span 1000 px in length
10, // the generation is sectioned in 10px intervals
[5, -5], // a range allowing random Y between the [min, max]
(dot, nextDot)=>{
dot.addConnection(nextDot) // adding a connection between each dot
}
)
// Creating a new shape containing the generated dots
const dummyShape = new Shape([0, 0], generatedDots)
// Adding the shape to the canvas
CVS.add(dummyShape)
```
### **To rotate or scale,** use the multiple prebuilt rotate/scale functions:
###### - rotateBy(deg, centerPos?), - rotateAt(deg, centerPos?), - rotateTo(deg, time, easing?, force?, centerPos?)
```js
// This will rotate all of dummyShape's dots around the shape's pos by 45 degrees
dummyShape.rotateBy(45)
// This will rotate all of dummyShape's dots around the pos [100, 100] at 180 degrees
dummyShape.rotateAt(180, [100, 100])
// This will smoothly rotate all of dummyShape's dots around the shape's pos at 125 degrees, over 5 seconds
dummyShape.rotateTo(125, 5000)
```
###### - scaleBy(scale, centerPos?), - scaleAt(scale, centerPos?), - scaleTo(scale, time, easing?, force?, centerPos?)
```js
// This will scale the distance between all of dummyShape's dots by 2x horizontally, from the point [250, 100]
dummyShape.scaleBy([2, 1], [250, 100])
// This will scale the distance between all of dummyShape's dots to 4x, from the shape's pos
dummyShape.scaleAt([4, 4])
// This will smoothly scale the distance between all of dummyShape's dots to 2.5x horizontally and
// 3.5x vertically, from the shape's pos, over 3 seconds
dummyShape.scaleTo([2.5, 3.5], 3000)
```
### **To remove a dot or the entire shape,** use the following functions:
###### - remove(), removeDot(dotOrId)
```js
// This will remove the dot with the id 10
dummyShape.removeDot(10)
// This will remove the first dot of the shape
dummyShape.removeDot(dummyShape.dots[0])
// This will remove the entire shape, along with its dots!
dummyShape.remove()
```
### **To duplicate a shape,** use the duplicate() function:
###### - duplicate()
```js
// Creating a dummy shape
const dummyShape = new Shape([10,10], new Dot())
// Adding it to the canvas and thus initializing it
CVS.add(dummyShape)
// Creating a copy of the dummyShape (which needs to be initialized)
const dummyShapeCopy = dummyShape.duplicate()
// Adding it to the canvas
CVS.add(dummyShapeCopy)
// Moving the copy 100px to the right
dummyShapeCopy.moveBy([100, 0])
```
#### Example use 1:
###### - Simple shape with small mouse effects
```js
// Creating a simple square-like shape
const simpleShape = new Shape([100,100],[
new Dot([-50, -50]),
new Dot([-50, 0]),
new Dot([-50, 50]),
new Dot([0, -50]),
new Dot([0, 50]),
new Dot([50, -50]),
new Dot([50, 0]),
new Dot([50, 50]),
], null, null, 100, (render, dot, ratio)=>{
// Changes the opacity and color according to mouse distance
dot.a = CDEUtils.mod(1, ratio, 0.8)
dot.r = CDEUtils.mod(255, ratio, -255)
dot.g = CDEUtils.mod(255, ratio, -255)
// Changes the dot's radius, from 2 times the default radius with a range of 80% (10px..2px), according to mouse distance
dot.radius = CDEUtils.mod(_Obj.DEFAULT_RADIUS*2, ratio, _Obj.DEFAULT_RADIUS*2*0.8)
// Draws a ring around the dot, at 5 times the radius
CanvasUtils.drawOuterRing(dot, [255,255,255,0.2], 5)
})
// Adding it to the canvas
CVS.add(simpleShape)
```
#### Example use 2:
###### - Single throwable dot, with color and radius effects
```js
const draggableDotShape = new Shape([0,0], new Dot([10,10]), null, null, null, (render, dot, ratio, setupResults, mouse, dist, shape)=>{
// Checking if the mouse is over the dot and clicked, and changing the color according to the state
const mouseOn = dot.isWithin(mouse.pos)
if (mouseOn && mouse.clicked) dot.color = [255, 0, 0, 1]
else if (mouseOn) dot.color = [0, 255, 0, 1]
else dot.color = [255, 255, 255, 1]
// Draws a ring around the dot, at 3 times the radius, only visible if the mouse is near
CanvasUtils.drawOuterRing(dot, [255,255,255,CDEUtils.mod(0.3, ratio)], 3)
// Making the dot drawable
const dragAnim = setupResults
dragAnim(shape.firstDot, mouse, dist, ratio)
}, null, (shape)=>{
// Accessing the dot
const dot = shape.firstDot
// Adding a simple infinite anim that changes the radius size back and forth
dot.playAnim(new Anim((progress, i)=>{
dot.radius = i%2?25*(1-prog):25*prog
}, -750, Anim.easeOutQuad))
// Getting the dragging animation callback
const dragAnim = CanvasUtils.getDraggableDotCB()
// Making it available in the drawEffectCB as setupResults
return dragAnim
})
// Adding it to the canvas
CVS.add(draggableDotShape)
```
#### Example use 3:
###### - Linking a shape pos to another one with anchorPos
```js
// Assuming we have simpleShape from example use 1 available...
// Creating a shape with a dot moving back and forth every second
const backAndForthDotShape = new Shape([200,200], new Dot([0,0], null, null, (dot, shape)=>{
let distance = 150, ix = dot.x
dot.playAnim(new Anim((progress, playCount, deltaTime)=>{
dot.x = ix + ((playCount % 2) == 0 ? 1 : -1) * distance * progress
if (progress == 1) ix = dot.x
}, -1000, Anim.easeOutBack))
})
)
// Setting simpleShape's anchorPos to the dot of backAndForthDotShape. (Using a callback since the dot doesn't exist yet)
simpleShape.anchorPos = () => backAndForthDotShape.firstDot
// Adding the shape to the canvas
CVS.add(backAndForthDotShape)
```
# [Filled Shape](#table-of-contents)
The FilledShape class is a derivative of the Shape class. It allows to fill the area delimited by the shape's dots.
#### **The FilledShape constructor takes the following parameters:**
###### - `new FilledShape(fillColor, dynamicUpdates, pos, dots, radius, color, limit, drawEffectCB, ratioPosCB, setupCB, loopCB, anchorPos, activationMargin, fragile)`
- *pos, dots, radius, color, limit, drawEffectCB, ratioPosCB, setupCB, loopCB, anchorPos, activationMargin, fragile* -> See the Shape class.
- **fillColor** -> Defines the color of the shape's filling. Either a color value, a Gradient instance, or a callback returning any of the previous two `(render, shape)=>{... return [r, g, b, a]}`.
- **dynamicUpdates** -> Whether the shape's fill area checks for updates every frame
#### **To update the fill area manually:** use the `updatePath()` function:
###### - `updatePath()`
```js
// ... Assuming there is a dummyFilledShape existing somewhere
// Moving it somewhere else
dummyFilledShape.moveAt(250, 250)
// If the dynamicUpdates parameter isn't set to 'true', the fill area will ONLY update IF the updatePath() function is called
dummyFilledShape.updatePath()
```
#### Example use 1:
###### - Simple red square
```js
// Creating a simple filledShape
const myFilledShape = new FilledShape(
"red", // color of the fill area
true, // Automatically updates the fill area positions
[100, 100], // shape pos
[
new Dot([-50, -50]), // Top left corner
new Dot([50, -50]), // Top right corner
new Dot([50, 50]), // Bottom right corner
new Dot([-50, 50]) // Bottom left corner
]
)
// Adding it to the canvas
CVS.add(myFilledShape)
```
#### Example use 2:
###### - Comparing default bounds vs accurate bounds
```js
// Creating a FilledShape with a complex shape
const someObj = new FilledShape([75,75,75,1], true, CVS.getCenter(), [new Dot([0,0]),new Dot([100,-50]),new Dot([300,50]),new Dot([100,80]),new Dot([270,-90]),new Dot([-70,-90]),new Dot([-150,190])], 3, [100,100,100,1])
// Setting a slow rotation / scaling animation, repeating every 60 seconds
someObj.setupCB = (obj)=>{
obj.playAnim(new Anim(prog=>obj.rotateAt(prog*360), -60000))
obj.playAnim(new Anim((prog, i)=>obj.scaleAt([0.25+(i%2?prog:(1-prog)), 0.25+(i%2?prog:(1-prog))]), -60000))
}
// drawing the both the raw outline and accurate outline
someObj.loopCB = (obj)=>{
CanvasUtils.drawOutline(CVS.render, obj)
CanvasUtils.drawOutlineAccurate(CVS.render, obj)
}
// Enabling accurate move listeners mode, this makes the mouse enter/exit events accurate when the object is moving
CVS.enableAccurateMouseMoveListenersMode()
// Listeners for enter/exit/click on the object, using the default bounds (red rectangle)
CVS.mouse.addListener(someObj, Mouse.LISTENER_TYPES.ENTER, ()=>console.log("FAST - enter"))
CVS.mouse.addListener(someObj, Mouse.LISTENER_TYPES.EXIT , ()=>console.log("FAST - exit"))
CVS.mouse.addListener(someObj, Mouse.LISTENER_TYPES.DOWN , ()=>console.log("FAST - click"))
// Listeners for enter/exit/click on the object, using the accurate bounds (blue outline)
CVS.mouse.addListener(someObj, Mouse.LISTENER_TYPES.ENTER, ()=>console.log("ACCURATE - enter"), true)
CVS.mouse.addListener(someObj, Mouse.LISTENER_TYPES.EXIT , ()=>console.log("ACCURATE - exit") , true)
CVS.mouse.addListener(someObj, Mouse.LISTENER_TYPES.DOWN , ()=>console.log("ACCURATE - click"), true)
// Adding the object to the canvas
CVS.add(someObj)
```
# [Grid](#table-of-contents)
The Grid class is a derivative of the Shape class. It allows the creation of dot-based symbols / text. To create your own set of symbols (source), see the *Grid Assets* section.
#### **The Grid constructor takes the following parameters:**
###### - `new Grid(keys, gaps, spacing, source, pos, radius, color, limit, drawEffectCB, ratioPosCB, setupCB, loopCB, anchorPos, activationMargin, fragile)`
- *pos, radius, color, limit, drawEffectCB, ratioPosCB, setupCB, loopCB, anchorPos, activationMargin, fragile* -> See the Shape class.
- **keys** -> A string containing the characters to create.
- **gaps** -> The `[x, y]` distances within the dots.
- **source** -> The source containing the symbol's definitions. See the *Grid Assets* section.
- **spacing** -> The distance between each symbol. (Letter spacing)
#### Example use 1:
###### - Displaying all A-Z letters, with a nice effect when passing the mouse over the shape
```js
// Creating a Grid
const coolAlphabet = new Grid(
"abcdefg\nhijklm\nnopqrs\ntuvwxyz", // the keys corresponding to the alphabet letters, with some line breaks
[5, 5], // equal gaps, this will make the alphabet look a bit square-ish
50, // 50px letter spacing
GridAssets.DEFAULT_SOURCE, // default source
[10,10], // the shape position (The text will start from this point, as its top-left corner)
2, // 2px dot radius
null, // color is left undefined, the shape will assign it the default value
null, // limit is left defined, default value assigned (100)
(render, dot, ratio)=>{ // This is the drawEffectCB, which gets call for every dot of the shape, every frame
// This will make a nice proximity effect when the mouse is close.
// The mod() function and the ratio allow us to modify the dot radius with
// a linear interpolation based on the distance between the ratioPos (the mouse) and the current dot.
dot.radius = CDEUtils.mod(_Obj.DEFAULT_RADIUS, ratio, _Obj.DEFAULT_RADIUS) // DEFAULT_RADIUS = 5
// By default, no connections are drawn between the dots of a grid.
// We can use the drawDotConnections() function to draw them easily.
CanvasUtils.drawDotConnections(dot, [255,0,0,1])
}
)
// The grid is done and dusted, adding it to the canvas
CVS.add(coolAlphabet)
```
#### Example use 2:
###### - Creating a grid, that gets distorted around the area of the mouse
```js
// Creating a grid with symbols that distort themselves on mouse hover
const distortedGrid = new Grid(
"abc\n123\n%?&", // symbols used
[7, 7], // gaps of 7px between each dot
50, // spacing of 50px between symbols
null, // using the default source
[100, 100], // pos
2, // dot's radius
"aliceblue",// dot's color
50, // limit of 50px
(render, dot, ratio, filterId)=>{ // grid's drawEffectCB
const scaleValue = CDEUtils.mod(50, ratio), // the scale value adjusted by the distance of the mouse
hasFilter = scaleValue>>0, // whether the current dot is affected by the filter (IMPORTANT FOR PERFORMANCES)
filterValue = hasFilter ? "url(#"+filterId+")" : "none" // adjusting the filter value
// accessing the <feDisplacementMap> element of the filter and updating its scale attribute
Canvas.getSVGFilter(filterId)[1].setAttribute("scale", scaleValue)
// drawing the symbols and applying some simple style changes, as well as the (↓) distortion filter. /!\ Also setting the (↓) "forceBatching" parameter to whether the filter is active or not
CanvasUtils.drawDotConnections(dot, render.profile5.update([255,0,0,1], filterValue, null, 1, 3), null, null, null, !hasFilter)
// finishing with a simple opacity effect for the dots
dot.a = CDEUtils.mod(1, ratio)
}, null, ()=>{ // grid's setupCB
// filter id to be used for the filter url
const filterId = "myFilter"
// loading a simple custom distortion SVG filter
Canvas.loadSVGFilter(`<svg>
<filter id="turbulence">
<feTurbulence type="turbulence" baseFrequency="0.01 0.02" numOctaves="1" result="NOISE"></feTurbulence>
<feDisplacementMap in="SourceGraphic" in2="NOISE" scale="25">
</feDisplacementMap>
</filter>
</svg>`, filterId)
return filterId
})
// Adding the grid to the canvas
CVS.add(distortedGrid)
```
# [Grid Assets](#table-of-contents)
This class contains the source declarations used by the Grid class. A source is basically a set of symbols, just like a font is a set of letters / numbers. You can create your own symbols and sources using the Grid Assets class. Sources are simply mapping *key* (any character, "A", "0", "%", ...) to *symbol* (a custom formation of dots).
### **The '`D`' constant:**
`D` (for directions) is a prebuilt object providing the cardinal and intercardinal directions. These are used to connect the dots together and draw specific lines between them, to create a symbol.
### **Creating a custom source:**
A source is an object that should contain:
- The width and height of all its symbols. (As of now, the width and height must be equal)
- The symbol's definitions. (The key should be in uppercase)
Ex: `{ width: 3, height: 3, A: [...], B: [...], 5: [...] }`
### **Creating custom symbols:**
A symbol has this structure: `[...[index, directions]]`. It is composed of a main array, containing the sub-arrays.
- The main array defines the vertical layers, a new layer is created each time a horizontal index is lower than the last.
- The sub-arrays each define a dot and contain its horizontal index and the directions of its connections.
**Example for creating the letter "A" in a 5x5 source:**
###### The final result should be dots placed like this
| Top-Left | 0 | 1 | 2 | 3 | 4 | Top-Right |
| - |:-:|:-:|:-:|:-:|:-:|:-: |
| 0 | | | o | | ||
| 1 | | o | | o | ||
| 2 | o | o | o | o | o ||
| 3 | o | | | | o ||
| 4 | o | | | | o ||
|**Bottom-Left**||||||**Bottom-Right**|
### **The steps to create a symbol:**
1. **Creating the main array** -> `[ ]`
```js
// The main array
[
]
```
2. **Creating the 1st vertical layer (*vertical index 0*).**
Since the "A" symbol doesn't have any dot at (0, 0) and (1, 0), we need to place the first dot at the cords (2, 0).
To do that, we only need to specify the horizontal index in the sub-array. -> `[2]`.
```js
// The main array
[
[2] // A dot at (2, 0)
]
```
3. **Adding connections.** A dot is now placed at (2,0), but it still has no connection to any other dot. To add connections,
we use the '`D`' constant. In this example, the needed connections are with the dots at (1, 1) and (3, 1). To achieve these,
we add a bottom-left (bl) connection and a bottom-right (br) connection, as the second parameter of the sub-array -> `[2, D.bl + D.br]`
**Note:** always define connections from upper dots to lower dots, and from left to right, to avoid redundancy.
```js
// The main array
[
[2, D.bl + D.br] // A dot at (2, 0), with connections to any dot at (1, 1) and (3, 1)
]
```
4. **Creating the 2nd vertical layer (*vertical index 1*).** Since the first layer only had a single dot, it is now completed, with the correct placement and connections. Let's continue the example with the 2nd layer.
This layer has dots only at (1, 1) and (1, 3), so we can already create the following sub-arrays -> [1,] and [3,]. Looking at the "A" graph above, the entire 3rd layer (*vertical index 2*) is filled with dots.
Though, to make the letter "A", we need to only connect to the dots at (0, 2) and (4, 2).
We achieve these two connections by updating our previous sub-arrays like this -> [1, D.bl] and [3, D.br]
**Note:** A new vertical layer is created when the sub-array horizontal index is smaller than the previous sub-array's. (When the horizontal index is negative, it forces the creation of a new vertical layer and starts it at the absolute value of the negative horizontal index) (You can also use `Infinity` to "skip" a layer without putting any dot)
```js
// The main array
[
// Here, we are at the vertical layer (vertical index 0)
[2, D.bl + D.br], // A dot at (2, 0), with connections to any dot at (1, 1) and (3, 1)
// Here, a new vertical layer is created (vertical index 1), because the first sub-array's horizontal index ([1, D.bl] -> 1) is smaller than the previous sub-array's horizontal index ([2, D.bl + D.br] -> 2)
[1, D.bl], [3, D.br] // A dot at (1, 1), with connections to any dot at (0, 2) and another dot at (1, 3), with connections to any dot at (4, 2)
]
```
5. **Continue the process until the symbol is fully formed**. In the end, you should have something like this:
```js
// The main array
[
// Here, we are at the vertical layer (vertical index 0)
[2, D.bl + D.br], // A dot at (2, 0), with connections to any dot at (1, 1) and (3, 1)
// Here, a new vertical layer is created (vertical index 1), because the first sub-array's horizontal index ([1, D.bl] -> 1) is smaller than the previous sub-array's horizontal index ([2, D.bl + D.br] -> 2)
[1, D.bl], [3, D.br], // A dot at (1, 1), with connections to any dot at (0, 2) and another dot at (1, 3), with connections to any dot at (4, 2)
// (vertical index 2)
[0, D.r + D.b], [1, D.r], [2, D.r], [3, D.r], [4, D.b],
// (vertical index 3)
[0,D.b], [4,D.b],
// (vertical index 4)
[0], [4] // Dots at the bottom (0, 4) and (4, 4), connected by the dots at vertical layer 3, but not initiating any connections themselves
]
// Uncommented
[
[2,D.bl+D.br],
[1,D.bl],[3,D.br],
[0,D.r+D.b],[1,D.r],[2,D.r],[3,D.r],[4,D.b],
[0,D.b],[4,D.b],
[0],[4]
]
```
#### Particularities:
- Leaving a sub-array's horizontal index empty (ex: `[,D.br]`), will result in it taking in value the increment of the previous sub-array's horizontal index. Ex -> `[2, D.br]`, `[, D.bl]` (here `[, D.bl]` is equal to `[3, D.bl]`).
- Leaving a sub-array's connections parameter empty (ex: `[2]`), will make it so the dot does not initiate any connection.
- Leaving a sub-array completely empty (ex: `[]`) logically implies that a dot will be created at the next horizontal index and that it won't initiate connections.
- **Important**: You cannot start a new vertical layer using a sub-array without a horizontal index (empty).
### **In the end, the example source should look like this:**
```js
{
width:5,
height:5,
A: [
[2,D.bl+D.br],
[1,D.bl],[3,D.br],
[0,D.r+D.b],[1,D.r],[2,D.r],[3,D.r],[4,D.b],
[0,D.b],[4,D.b],
[0],[4]
],
// Other symbols ...
}
```
# [TextDisplay](#table-of-contents)
The TextDisplay class allows the drawing of text as a canvas object.
#### **The TextDisplay constructor takes the following parameters:**
###### - `new TextDisplay(text, pos, color, textStyles, drawMethod, maxWidth, setupCB, loopCB, anchorPos, activationMargin)`
- *pos, color, setupCB, loopCB, anchorPos, activationMargin* -> See the _Obj / *_BaseObj* class.
- **text** -> The text to be displayed. Either a `String` or a callback `(parent, this)=>{... return "textToDisplay"}`.
- **textStyles** -> The style profile to be used for styling the text. Either a `TextStyles` or a callback `(render)=>{... return TextStyles}`.
- **drawMethod** -> The draw method used when drawing the text, Either `"FILL"` or `"STROKE"`.
- **maxWidth**? -> The max width in pixels of the drawn text.
**Its other attribute is:**
- **size** -> The text's *width* and *height* in pixels `[width, height]`. Does not take into account scaling, use the `trueSize` getter for adjusted size.
#### Example use 1:
###### - Drawing a simple spinning text
```js
const helloWorldText = new TextDisplay(
"Hello World!", // Displayed text
[200, 100], // positioned at [200, 100]
"lightblue", // colored light blue
(render)=>render.textProfile1.update("italic 24px monospace"), // using the textProfile1 styles, only over writing the font
null, // leaving drawMethod to the default value ("FILL")
null, // leaving maxWidth to the default value (undefined)
(textDisplay)=>{// setupCB
// adding a spin animation, repeating every 3 seconds
textDisplay.playAnim(new Anim(prog=>{
// Updating the horizontal scale to go from 1, to -1, back to 1
textDisplay.scale = [Math.sin(Math.PI*prog*2), 1]
},-3000))
})
// Adding the object to the canvas.
CVS.add(helloWorldText)
```
# [ImageDisplay](#table-of-contents)
The ImageDisplay class allows the drawing of images, videos and live camera/screen feeds.
#### **The ImageDisplay constructor takes the following parameters:**
###### - `new ImageDisplay(source, pos, size, errorCB, setupCB, loopCB, anchorPos, activationMargin)`
- *pos, setupCB, loopCB, anchorPos, activationMargin* -> See the _Obj / *_BaseObj* class.
- **source** -> The source of the image. One of `ImageDisplay.SOURCE_TYPES`.
- **size** -> The display size of the image `[width, height]`. (Resizes the image)
- **errorCB** -> A callback called when the source produces an error `(errorType, e?)=>`.
**Its other attributes are:**
- **sourceCroppingPositions** -> The source cropping positions. Delimits a rectangle which indicates the source drawing area to draw from: `[ [startX, startY], [endX, endY] ]`. (Defaults to no cropping)
#### Example use 1:
###### - Drawing an image from the web
```js
// Creating an ImageDisplay with a url pointing to an image
const myCoolImage = new ImageDisplay("https://static.wikia.nocookie.net/ftgd/images/9/97/ExtremeDemon.png/revision/latest?cb=20240801164829")
// Adding the object to the canvas.
CVS.add(myCoolImage)
```
#### Example use 2:
###### - Drawing an image from a file, resizing it, and cropping it
```js
// Creating an ImageDisplay by loading a local file, and adjusting the sizes
const myCoolImage = new ImageDisplay(
"./img/logo.png", // local file located at [currentFolder]/img/logo.png
[0,0], // position of the top-left corner
[100, 100] // rezises the image to 100x100
)
// Cropping the source image to use only from [20, 20] to [150, 150]
myCoolImage.sourceCroppingPositions = [[20,20], [150, 150]]
// Adding the object to the canvas.
CVS.add(myCoolImage)
```
#### Example use 3:
###### - Drawing and playing a video
```js
// Creating an ImageDisplay playing a video
const dummyVideo = new ImageDisplay(
"./img/vidTest.mp4", // local file located at [currentFolder]/img/vidTest.mp4
null, // using default pos ([0, 0])
null, // using natural size (default)
(video)=>{
// SetupCB, runs when the source is loaded
// Automatically starts the video once loaded
video.playVideo()
}
)
// Adding the object to the canvas.
CVS.add(dummyVideo)
```
#### Example use 4:
###### - Drawing live feeds from the camera and screen
```js
// Creating an ImageDisplay displaying the camera (requires user permission)
const cameraFeed = new ImageDisplay(
ImageDisplay.loadCamera(), // get the camera, with default settings
[0,0], // draw at origin
ImageDisplay.RESOLUTIONS.SD // shrink it down to 640x480
)
// Creating an ImageDisplay displaying a screen (requires user actions and permission)
const screenFeed = new ImageDisplay(
ImageDisplay.loadCapture(), // get the screen capture, with default settings
[640,0], // draw next to the camera display
[1920/4, 1080/4] // resize it to Full HD divided by 4
)
// Adding both objects to the canvas.
CVS.add(cameraFeed)
CVS.add(screenFeed)
```
**Note:** Canvas image smoothing property is disabled by default to improve performances.
# [AudioDisplay](#table-of-contents)
The AudioDisplay class allows the visualization of audio from song, videos, live microphone / computer audio, etc, in cutomizable forms.
#### **The AudioDisplay constructor takes the following parameters:**
###### - `new AudioDisplay(source, pos, color, binCB, sampleCount, disableAudio, offsetPourcent, loadErrorCB, setupCB, loopCB, anchorPos, activationMargin)`
- *pos, color, setupCB, loopCB, anchorPos, activationMargin* -> See the _Obj / *_BaseObj* class.
- **source** -> The source of the audio. One of `AudioDisplay.SOURCE_TYPES`.
- **binCB** -> A custom callback called for each bin of the FFT data array. Used to draw the audio. `(render, bin, atPos, accumulator audioDisplay, i, sampleCount, rawBin)=>{... return? [ [newX, newY], newAccumulatorValue ]}`
- **sampleCount** -> The count of bins to use / display. Ex: if sampleCount is "32" and the display style is `BARS`, 32 bars will be displayed. *Note: (fftSize is calculated by selecting the nearest valid value based on twice the provided sampleCount).*
- **disableAudio** -> Whether this AudioDisplay actually output outputs sounds/audio. *Note: (This value does not affect the visual display, only whether you can hear what is playing or not).*
- **offsetPourcent** -> A number between 0 and 1 representing the offset pourcent in the order of the bins when calling binCB.
- **errorCB** -> A callback called when the source produces an error `(errorType, e?)=>`.
- **transformable** -> If above 0, allows transformations with non batched canvas operations. (Mostly managed automatically)
**Its other attributes are the following audio context / analyser / modifier nodes:**
*(see [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) for more information)*
- *audioCtx*
- *audioAnalyser*
- *gainNode*
- *biquadFilterNode*
- *convolverNode*
- *waveShaperNode*
- *dynamicsCompressorNode*
- *pannerNode*
- *delayNode*
**Note:** ↑ *The audio chain is also defined in the above order.*
#### Example use 1:
###### - Displaying the waveform of a .mp3 file
```js
// Creating an AudioDisplay playing and displaying a local file
const audioDisplay = new AudioDisplay(
"./audio/song.mp3", // the filepath of the .mp3 file
[100,100], // the pos of the display
"lime", // color of the display
AudioDisplay.BARS(),// the display type (here we use the generic bars/waveform display)
64, // the sample count, here 64 bars will be displayed (and the fftSize will be 128)
false, // not disabling the audio so we can hear the song.mp3 playing
0, // no offset
(type, e)=>console.log("Dang it, error! Type:", type, " | ", e) // onerror callback
)
// Adding the object to the canvas.
CVS.add(audioDisplay)
```
#### Example use 2:
###### - Displaying the microphone output as a circle
```js
// Creating an AudioDisplay displaying the microphone
const micDisplay = new AudioDisplay(
AudioDisplay.loadMicrophone(), // loading the microphone
[100,100], // the pos of the display
"lime", // color of the display
(render, bin, pos, audioDisplay, accumulator, i)=>{// binCB
const maxRadius = 500/AudioDisplay.MAX_NORMALISED_DATA_VALUE, // defining a max radius of 500px
precision = 100 // used to skip over some bins (the lowest this is, the more precise the display will be, but the more performance-heavy it will be too!)
// optimizing with the precision variable (only drawing every <precision> time)
if (i%precision==0) {
// drawing the circles, the radius is based on the current bin value, and it's style is based on this audioDisplay object's styles
render.batchStroke(Render.getArc(pos, maxRadius*bin), audioDisplay._color, audioDisplay.visualEffects)
}
},
2000, // the sample count, here a maximum of 2000 bins would be displayed (and the fftSize will be 4096)
true, // disabling the audio to prevent echo
)
// Adding the object to the canvas.
CVS.add(micDisplay)
```
#### Example use 3:
###### - Displaying the screen's audio and applying some audio modifications
```js
// Loading and displaying the screen audio. For this, the user needs to select a valid tab/window/screen.
const audioDisplay = new AudioDisplay(AudioDisplay.loadScreenAudio(),