Package Exports
- cdejs
Readme
CanvasDotEffect
CanvasDotEffect is a lightweight JS library that helps create customizable and interactive dot-based effects using the Canvas API.
Table of Contents
- Getting Started / Minimal setup
- Classes
- Execution order
- Optimization
- Intended practices
- React Component Template
- Credits
Getting Started / Minimal setup
Either run: npx cdejs-template yourFolderName
or follow these couple steps! (↓)
- Get the library file. (
npm install cdejs
or canvasDotEffect.min.js)
<!-- Only if you're using the browser version! Otherwise use: import {...} from "cdejs" -->
<script src="canvasDotEffect.min.js"></script>
- In your HTML file, place a canvas element. The canvas will later automatically take the size of its parent element.
<div class="canvasHolder">
<canvas id="canvasId"></canvas>
</div>
- In a JS file, create a new Canvas instance with the HTML canvas element in parameter.
const CVS = new Canvas(document.getElementById("canvasId"))
- From this Canvas instance, add any canvas objects you want.
// Create a canvas object
const dummyShape = new Shape([50, 50], [new Dot()])
// Add it to the canvas
CVS.add(dummyShape)
- Set the mouse event listeners for mouse interactions.
// Set up the prebuilt event listeners, allowing the creation of more interactive effects!
CVS.setMouseMove(/*custom callback*/)
CVS.setMouseLeave()
CVS.setMouseDown()
CVS.setMouseUp()
- Once everything is created and ready to go, start the drawing loop!
// Start
CVS.startLoop()
- In the end, you should have something like this:
const CVS = new Canvas(document.getElementById("canvasId"))
// Creating and adding shapes ...
const dummyShape = new Shape([50, 50], [new Dot()])
CVS.add(dummyShape)
CVS.setMouseMove(/*custom callback*/)
CVS.setMouseLeave()
CVS.setMouseDown()
CVS.setMouseUp()
CVS.startLoop()
Note: if you are using de NPM version of this librairy, using Vite or any other bundler is recommended.
- Minimal example package.json
{
"name": "my_project",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"dependencies": {
"cdejs": "^1.0.16"
},
"devDependencies": {
"vite": "^6.2.2"
}
}
Class descriptions
The following sections are short documentations of each class, basically what it does and what are the most important aspects of it.
Canvas
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, visibilityChangeCB, cvsFrame, settings, willReadFrequently)
- cvs -> The HTML canvas element to link to.
- loopingCB? -> A callback ran each frame.
(Canvas)=>
- fpsLimit? -> The maximum fps cap. Defaults to V-Sync.
- visibilityChangeCB? -> A callback called on document visibility change.
(isVisible, cvs, event)=>
- 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 tofalse
)
To add objects to the canvas, use the add() function:
- add(objs, inactive=false)
// 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:
// 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:
// Starts the main loop
CVS.startLoop()
// Stops the main loop
CVS.stopLoop()
Example use:
- Creating a Canvas instance that displays fps
// Creating a FPSCounter instance
const fpsCounter = new CDEUtils.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
The _Obj class is the template class of any 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)=>
- 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. - alwaysActive -> Whether the object stays active when outside the canvas bounds.
- 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
androtateTo
functions to modify. - scale -> The shape's X and Y scale factors
[scaleX, scaleY]
. Use thescaleAt
,scaleBy
andscaleTo
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 ofRender.COMPOSITE_OPERATIONS
(see global composite operations 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
, ...) - Access to the object's animation play (
playAnim
)
The follow function: use follow()
to make an object follow a custom path:
- follow(duration, easing, action, progressSeparations)
/**
* Used to make an object follow a custom path
* @param {Number} duration: duration of the animation in ms
* @param {Function} easing: 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
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, alwaysActive, disablePathCaching)
- pos, radius, color, setupCB, anchorPos, alwaysActive -> 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 thedisablePathCaching
constructor parameter or by thedisablePathCaching()
function)
To add or remove connections, use the following functions:
// 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:
// 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:
// Removes the dot completely
dot.remove()
Example use 1:
- A dot on its own (rare)
// 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
// 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
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, alwaysActive, fragile)
- pos, radius, color, setupCB, loopCB, anchorPos, alwaysActive -> 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 tonull
. - fragile? -> 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)
// 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:
- Sets the dots' the dots' color to the one of shape, if not previously defined.
- Sets the dots' radius to the one of the shape, if not previously defined.
- Sets the dots' anchorPos to the shape's pos, if not previously defined.
- Sets the dots' alwaysActive property to that of the shape, if not previously defined.
- 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()
// 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)
// 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?)
// 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?)
// 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)
// 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()
// 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
// 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, normalColorTester, 100, (render, dot, ratio, setupResults, mouse)=>{
// 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
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, true)
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 an 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
// 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
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, alwaysActive, fragile)
- pos, dots, radius, color, limit, drawEffectCB, ratioPosCB, setupCB, loopCB, anchorPos, alwaysActive, 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()
// ... 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
// 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)
Grid
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, alwaysActive, fragile)
- pos, radius, color, limit, drawEffectCB, ratioPosCB, setupCB, loopCB, anchorPos, alwaysActive, 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
// 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
// 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
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:
Creating the main array ->
[ ]
// The main array [ ]
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]
.// The main array [ [2] // A dot at (2, 0) ]
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.
// The main array [ [2, D.bl + D.br] // A dot at (2, 0), with connections to any dot at (1, 1) and (3, 1) ]
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)// 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) ]
Continue the process until the symbol is fully formed. In the end, you should have something like this:
// 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:
{
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
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, alwaysActive)
- pos, color, setupCB, loopCB, anchorPos, alwaysActive -> 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 thetrueSize
getter for adjusted size.
Example use 1:
- Drawing a simple spinning text
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
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, alwaysActive)
- pos, setupCB, loopCB, anchorPos, alwaysActive -> 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
// 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
// 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
// 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
// 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
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, alwaysActive)
- pos, color, setupCB, loopCB, anchorPos, alwaysActive -> 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?)=>
.
Its other attributes are the following audio context / analyser / modifier nodes:
(see 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
// 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
// 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
// Loading and displaying the screen audio. For this, the user needs to select a valid tab/window/screen.
const audioDisplay = new AudioDisplay(AudioDisplay.loadScreenAudio(), [100,100], "lime", AudioDisplay.BARS(), 64)
// Applying some audio modifiers:
// Setting the volume to 200%
audioDisplay.setVolume(2)
// Sets the audio filter to keep low frequencies and cut high ones
audioDisplay.setBiquadFilterType(AudioDisplay.BIQUAD_FILTER_TYPES.LOWPASS)
// delays the audio by 1 second
audioDisplay.setDelay(1)
// sets the distortion level to 10
audioDisplay.setDistortionCurve(10)
// sets the origin of the audio to seem like it's coming from 1 meter to the left
audioDisplay.setOriginPos(-1)
// since this sounds pretty bad, you could also reset all the modifiers with this:
//audioDisplay.resetAudioModifiers()
// Adding the object to the canvas.
CVS.add(audioDisplay)
Example use 4:
- Creating a reverb effect using the convolverNode
// Taking back the audioDisplay from example 1.
const audioDisplay = new AudioDisplay("./audio/song.mp3", [100,100], "lime", AudioDisplay.BARS(), 64)
// For this to work, you need an impulse response file
// This will load the IR and, by default, assign its buffer to the convolverNode then connect the latter. (You also can handle this yourself by specifying the second parameter: "readyCallback")
audioDisplay.loadImpulseResponse("./audio/IR.wav")
// To disable this effect, disconnect the convolverNode:
// audioDisplay.disconnectConvolver()
// Adding the object to the canvas.
CVS.add(audioDisplay)
Note: Due to the high customizability of the display, the getAutomaticPosition
function of Pattern/Gradient classes is not available, therefore the positions need to be entered manually.
Color
The Color class represents a color and provides multiple utility functions such as HSV control, RGBA control, color finding, color format conversions, and color normalization.
Note: Direct HSV / RGBA controls are not supported for gradients
The Color constructor takes the following parameters:
- new Color(color, isChannel)
- color -> The color value in any supported format. (Text:
"red"
, RGBA:[255, 255, 255 ,1]
, HEX:"#123abcff", Gradient:new Gradient( ... )
, Color: an instance of this class) - isChannel -> If
true
, the Color instance will be considered a color channel and will not duplicate. (Channels are for global color distribution)
To convert a color to another format, use the convertTo() function:
- converTo(format=Color.FORMATS.RGBA, color)
const red = "red"
console.log("Here is red in some other formats:",
Color.convertTo(Color.FORMATS.RGBA, red)
Color.convertTo(Color.FORMATS.HSV, red)
Color.convertTo(Color.FORMATS.HEX, red)
)
Note: ConvertTo
doesn't support gradients.
To find the first position of a color in the canvas, use the findFirstPos() function:
- findFirstPos(canvas, color, useAlpha=false, temperance=Color.DEFAULT_TEMPERANCE, searchStart=Color.DEFAULT_SEARCH_START, areaSize=[])
const redColor = new Color([255, 0, 0, 1])
const whereRedIsAt = Color.findFirstPos(
CVS, // Canvas instance
redColor // Color instance
)
if (whereRedIsAt) console.log("The color red was found at: X:"+whereRedIsAt[0]+", Y:"+whereRedIsAt[1])
else console.log("The canvas doesn't contain the color red")
Gradient
The Gradient class allows the creation of custom linear / radial gradients. A Gradient instance can be used in the color and fillColor fields of canvas objects.
The Gradient constructor takes the following parameters:
- new Gradient(render, positions, colorStops, type, rotation)
- render -> The canvas Render instance, or context.
- positions -> The positions of the gradient. Providing a canvas object will automatically position it to cover the minimal area containing all of the provided objects. For manual positions: linear gradients:
[ [x1, y1], [x2, y2] ]
, radial gradients[ [x1, y1, r1], [x2, y2, r2] ]
, conic gradients:[ x, y ]
. - colorStops -> An array containing the difference colors and their range
[0..1, color]
. Ex:[ [0, "purple"], [0.5, [255,0,0,1]], [1, "#ABC123"] ]
. - type -> The type of gradient. Either: Linear, Radial or Conic. (Defaults to Linear)
- rotation -> The rotation in degrees of the gradient. (Not applicable for Radial gradients)
To manually update a Gradient, use the update() function:
- update()
// Creating a gradient
const customGradient = new Gradient(
CVS.ctx, // canvas context
[ [0, 0], [100, 100] ], // setting manual position
[[0, "red"], [1, "blue"]], // goes from red to blue
Gradient.TYPES.LINEAR // linear gradient
)
// Assigning the gradient to a dummy filled shape
dummyFilledShape.fillColor = customGradient
// Ex: Updating color stops
customGradient.colorStops = [[0, "green"], [1, "pink"]]
// Access the gradient assigned to the shape's filling and update it
dummyFilledShape.fillColorRaw.update()
Note: when using a Shape, a Dot, or a TextDisplay instance as the 'positions' parameter, the gradient will update every frame automatically.
Example use 1:
- Coloring a FilledShape with a gradient and making a rotating gradient effect
const gradientShape = new FilledShape(
// Creating and returning a linear gradient with a callback.
// This linear gradient will auto-position itself according to the shape's dots, start at 90deg rotation, and will go from purple->red->yellow-ish
(render, shape)=>new Gradient(render, shape, [[0, "purple"], [0.5, [255,0,0,1]], [1, "#ABC123"]], null, 90),
// Other parameters are used by the FilledShape, to make a square at [100, 100]
false,
[200, 200],
[
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
]
)
// Creating and playing the rotating gradient animation
gradientShape.playAnim(
new Anim((progress)=>{
// Getting the gradient instance
const gradient = gradientShape.fillColorRaw
// Rotating it
gradient.rotation = 360 * progress
}, -750) // repeating every 750 ms
)
// Adding it to the canvas
CVS.add(gradientShape)
Pattern
The Pattern class allows the creation image/video based colors. A Pattern instance can be used in the color and fillColor fields of canvas objects.
The Pattern constructor takes the following parameters:
- new Pattern(render, source, positions, sourceCroppingPositions, keepAspectRatio, forcedUpdates, rotation, errorCB, readyCB, frameRate, repeatMode)
- render -> The canvas Render instance, or context.
- source -> The source declaration of the pattern. One of
ImageDisplay.SOURCE_TYPES
. - positions -> The positions of the pattern. (
[ [x1, y1], [x2, y2] ]
) Providing a canvas object will automatically position it to cover the minimal area containing all of the provided objects. - 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) - keepAspectRatio -> Whether the displayed pattern keeps the same aspect ratio when resizing.
- forcedUpdates -> Whether/How the pattern updates are forced. One of
Pattern.FORCE_UPDATE_LEVELS
. - rotation -> The pattern's current rotation in degrees.
- errorCB -> A callback called when the source produces an error
(errorType, e?)=>
. - readyCB -> Similar to
setupCB
. A callback that gets called when the source has been initialized(pattern)=>
. Note: this attribute doesn't transfer when calling the duplicate() function to avoid StackOverflow errors. - frameRate -> The update frequency of the current source. (Controls the frequency of video/canvas sources updates, as well as the frequency of any other sources when a visible property gets updated: e.g when the rotation gets changed)
- repeatMode -> Whether the pattern repeats horizontally/vertically. One of
Pattern.REPETITION_MODES
.
To manually get the rectangular area containing all of a specific object, use the _DynamicColor.getAutomaticPositions() function:
- getAutomaticPositions(obj)
// Creating a dummy shape
const dummyShape = new Shape([0, 0], [new Dot([50,50]), new Dot([100, 0])])
// Adding the shape to the canvas first, because it needs to be initialized before looking at its pos
CVS.add(dummyShape)
// Getting the area (see example use 4 for real use of this function)
const area = _DynamicColor.getAutomaticPositions(dummyShape)
console.log("My Dummy Shape fits perfectly into this area! -> ", area)
Example use 1:
- Coloring some dots with a custom image
// Creating a dummy shape with two big dots, here parts of the image are going to be visible through the dots
const dummyShape = new Shape([100, 100], [new Dot([250,250]), new Dot([0,0])], 50, (render, shape)=>
new Pattern(
render, // the render instance
"./img/img2.jpg", // the source of the image, here it's the path to a local file
shape, // making the pattern fit the shape size
null, // no source cropping
true // preserving the default aspect ratio
)
)
// Adding the shape to the canvas
CVS.add(dummyShape)
Example use 2:
- Coloring some text with a camera feed
// Creating a dummy text display
const dummyText = new TextDisplay("Hey, this is just\n some random text in\n order to fill up space,\n have a nice day! :D", [250, 250], (render, text)=>
new Pattern(
render, // the render instance
Pattern.loadCamera(), // the source of the pattern, here it's we are requesting access to the live camera feed
text, // making the pattern fit the size of the text
null, // no source cropping
false, // resizing will most likely change the aspect ratio
Pattern.FORCE_UPDATE_LEVELS.RESPECT_FRAME_RATE // automatically resizes the pattern if the textDisplay size/pos changes
))
// Adding the text to the canvas
CVS.add(dummyText)
Example use 3:
- Sharing VS duplicating a pattern
// DUPLICATING PATTERN (Can be performance-heavy when using dynamic patterns such as video, camera, etc)
// Creating and adding a basic shape that contains 9 dots in a 3x3 grid disposition
const shape1 = new Shape([100,100],[new Dot([-50, -50]), new Dot([-50, 0]), new Dot([-50, 50]), new Dot([0, -50]), new Dot([0, 0]), new Dot([0, 50]), new Dot([50, -50]), new Dot([50, 0]), new Dot([50, 50])], 25)
CVS.add(shape1)
// Creating a pattern that will get duplicated for each dot of shape1 (set the "positions" (↓) parameter to the placeholder value)
const duplicatedPattern = new Pattern(CVS.render, Pattern.loadCamera(), Pattern.PLACEHOLDER, null, null, null, null, null,
(pattern)=>{// readyCB
// once the camera is loaded, set the shape1's color to the value of the pattern
shape1.setColor(pattern)
}
)
// SHARED PATTERN
// Creating and adding a basic shape that contains 9 dots in a 3x3 grid disposition
const shape2 = new Shape([300,100],[new Dot([-50, -50]), new Dot([-50, 0]), new Dot([-50, 50]), new Dot([0, -50]), new Dot([0, 0]), new Dot([0, 50]), new Dot([50, -50]), new Dot([50, 0]), new Dot([50, 50])], 25)
CVS.add(shape2)
// Creating a pattern that will get duplicated for each dot of shape1 (set the "positions" (↓) parameter to the area containing all the dots)
const sharedPattern = new Pattern(CVS.render, Pattern.loadCamera(), _DynamicColor.getAutomaticPositions(shape2), null, null, null, null, null,
(pattern)=>{// readyCB
// once the camera is loaded, set the shape2's color to the value of the pattern
shape2.setColor(pattern)
}
)
Example use 4:
- Using a pattern to color non-objects (In this case, lines)
// Creating a variable containing the color of the grid's symbol lines
let gridLineColor = [255,0,0,1]
// Creating a simple grid displaying a couple of letters
const grid = new Grid("abc\nDEF\nghi", [5, 5], 50, null, [50,50], 2, null, null,
(render, dot, ratio, res, m, dist, shape, isActive)=>{// drawEffectCB
// simple effect to change the dot's radius
dot.radius = CDEUtils.mod(_Obj.DEFAULT_RADIUS, ratio, _Obj.DEFAULT_RADIUS)
// drawing the grid's lines (connections) using the previous variable (↓). Note: the variable could also have been defined in the setupCB
CanvasUtils.drawDotConnections(dot, render.profile1.update(gridLineColor, null, null, null, 2, [0]))
}
)
// Adding the grid to the canvas. (This initializes it, which is needed to properly run getAutomaticPositions() on it)
CVS.add(grid)
// Assigning a pattern to the gridLineColor variable so it displays the camera as the color
gridLineColor = new Pattern(CVS.render, Pattern.loadCamera(), _DynamicColor.getAutomaticPositions(grid), null, null, null, null, null,
(pattern)=>{// readyCB
// (Optional): You can also set the dot's color to be this pattern.
// once the camera is loaded, we're also setting the grid's color to the value of the pattern
grid.setColor(pattern)
}
)
Render
Render is a class that centralizes most context operations. It provides functions to get lines and text, as well as functions to stroke / fill them. Most of the calls to this class are automated via other classes (such as Dot and FilledShape), except for the utility line getters which allow more customization. It also provides access to style profiles for lines and text (RenderStyles, TextDisplay). Finally, it is automatically instantiated by, and linked to, any Canvas instance and should not be instantiated manually.
Example use 1:
- Manually drawing a custom bezier curve
// Running in the drawEffectCB of a dummy shape...
{
...
// Drawing a bezier curve from [100, 100] to [100, 200], using the default control points, in red
render.stroke(Render.getBezierCurve([100,100], [100, 200]), [255, 0, 0, 1])
}
Example use 2:
- Manually drawing a custom filled quadratic curve
// Running in the drawEffectCB of a dummy shape...
{
...
// Drawing a bezier curve from [100, 400] to [400, 300], using the default control points with 0.5 spread
render.fill(Render.getQuadCurve(startPos, endPos, Render.getDefaultQuadraticControlPos([100, 400], [400, 300], 0.5)))
}
RenderStyles
The RenderStyles class allows the customization of renders via style profiles when drawing with the Render class. By default, the following profiles are created and accessible via any Render instance: defaultProfile
and profile1
, to profile5
. There is also a profiles
array to add more custom profiles.
The RenderStyles constructor takes the following parameters:
- new RenderStyles(render, color, filter, compositeOperation, opacity, lineWidth, lineDash, lineDashOffset, lineJoin, lineCap)
- render -> The canvas Render instance.
- color -> Either an RGBA array
[r, g, b, a]
or aColor
instance. - filter -> A string containing a filter in CSS formatting:
"blur(5px)"
,url(#svgFilterId)
, etc. (Usage of filters may cause some performance issues, and some SVG filters can be invasive) - compositeOperation -> The composite operation used. One of
Render.COMPOSITE_OPERATIONS
(see global composite operations for more information) (some composite operations can be invasive) - opacity -> The alpha value of the object ranging from 0 to 1. (This alpha is additive to the object's color alpha).
- lineWidth -> The width in px of the drawn line.
- lineDash -> Gaps length within the line
- lineDashOffset -> Offset in px of the start of the gaps (dashes).
- lineJoin -> Determines the shape of line joins. Either: miter, bevel or round
- lineCap -> Determines the shape of line ends. Either: butt, square or round
Note: See MDN Canvas API documentation for more information on line styling properties.
To create a new style profile, use the duplicate() function:
- duplicate(render?, color?, lineWidth?, lineDash?, lineDashOffset?, lineJoin?, lineCap?)
// Creating a new style profile from the RenderStyles' template profile
const myNewStyleProfile = RenderStyles.DEFAULT_PROFILE.duplicate(CVS.render)
// Adding a new style profile to the render's custom profile list
CVS.render.profiles.push(myNewStyleProfile) //The style profile is now accessible via render.profiles
// OR
// use the render instance function: createCustomStylesProfile()
To reuse a style profile for multiple styles, use the update() function:
- update(color?, lineWidth?, lineDash?, lineDashOffset?, lineJoin?, lineCap?)
{// Running in the drawEffectCB function of some shape...
// ...
// Drawing a line between a dot and its ratioPos, using the profile1 styles and updating the color, lineWidth, lineDash, lineDashOffset
CanvasUtils.drawLine(dot, dot.ratioPos, render.profile1.update(Color.rgba(0,255,255,CDEUtils.mod(1, ratio, 0.8)), 4, [5, 25], 10))
// Drawing a dot's connections, using again the profile1 styles and updating only the color, lineWidth
CanvasUtils.drawDotConnections(dot, render.profile1.update([255,0,0,1], 2))
}
Example use 1:
- Styling a bezier curve (based on the example in Render)
// Running in the drawEffectCB of a dummy shape...
{
...
// Drawing a bezier curve from [100, 100] to [100, 200], using the styles from the render's profile1
render.stroke(Render.getBezierCurve([100,100], [100, 200], [150, 100], [100, 150], render.profile1)
}
Example use 2:
- Styling connections
// Running in the drawEffectCB of a dummy shape...
{
...
// Drawing the connections between dots, and styling them with an updated version of render's profile1 and a custom lineType
CanvasUtils.drawDotConnections(dot, render.profile1.update(
Color.rgba(255, 0, 0, CDEUtils.mod(1, ratio, 0.8)), // updating the color to a dynamically shaded red
4, // updating the lineWidth to 4px
[10] // updating the lineDash to 10px
),
0, // no radius padding
Render.LINE_TYPES.CUBIC_BEZIER // using a default bezier curve
)
}
TextStyles
The TextStyles class (similar to TextStyles) allows the customization of text via style profiles when drawing text with the TextDisplay class. By default, the following profiles are created and accessible via any Render instance: defaultTextProfile
and textProfile1
to textProfile5
. There is also a textProfiles
array to add more custom profiles. (Most functions from TextStyles apply very similarly to TextStyles)
The TextStyles constructor takes the following parameters:
- new TextStyles(render, font, letterSpacing, wordSpacing, fontVariantCaps, direction, fontStretch, fontKerning, textAlign, textBaseline, textRendering)
- render -> The canvas Render instance.
- font -> The text font properties as a string. Ex: "italic 32px Arial"
- letterSpacing -> The gap in pixels between the letters.
- wordSpacing -> Then gaps in pixels between the words.
- fontVariantCaps -> Specifies an alternative capitalization style.
- direction -> Specifies the text writing direction.
- fontStretch -> The text stretching property.
- fontKerning -> Whether the default spacing of certain letters is uniform.
- textAlign -> The text horizontal alignment.
- textBaseline -> The text vertical alignment.
- textRendering -> The text rendering optimization method.
Note: See MDN Canvas API documentation for more information on text styling properties.
Anim
The Anim class allows the creation of smooth animations and the use of easings.
The Anim constructor takes the following parameters:
- animation -> Callback containing the animation code
(progress)=>{...}
. - duration -> The duration in milliseconds. (Negative values will make the animation repeat infinitely).
- easing -> The easing function to be used
(x)=>{... return x}
. - endCallback? -> Custom callback ran upon the animation ending.
To play an animation:
Use the playAnim() function on any canvas object. All objects have an anims
property containing the currents
and the backlog
animations.
The
currents
behave like this: By default, an animation is added to thecurrents
array. Every animation in thecurrents
array runs simultaneously.The
backlog
behaves like this: When theisUnique
parameter istrue
, animations get added to the end of the backlog queue of an object. Animations are then played one at a time, upon their arrival at the first index of the queue. Setting theforce
parameter totrue
terminates the current backlog animation and instantly replaces it with the providedanim
parameter.
- playAnim(Anim, isUnique, force)
Currents
animations:
// Dummy animations
const someAnim = new Anim(/* some parameters ... */)
const anotherAnim = new Anim(/* some other parameters ... */)
// This will run all both anims simultaneously
dummyShape.playAnim(someAnim)
dummyShape.playAnim(anotherAnim)
Backlog
animations:
// Dummy animations
const someUniqueAnim = new Anim(/* some parameters ... */)
const uniqueAnimationThatNeedsToRunNOW = new Anim(/* some other parameters ... */)
// This will queue the animation to be run once all previously queued animations are completed.
dot.playAnim(someUniqueAnim, true)
// Will terminate and replace any animation running! (After 3 seconds)
setTimeout(()=>{
dot.playAnim(animationThatNeedsToRunNOW, true, true)
}, 3000)
To reset an animation, use the reset() function:
// This will reset the animation to progress=0
anim.reset()
Example use 1:
- Fading a dot and deleting it once it's fully invisible
// Getting a dot
const dot = dummyShape.dots[0]
// Creating the animation
const fadingAnimation = new Anim(
(progress)=>{
// fading off the dot over 5 seconds
dot.a = 1 - progress
// deleting the dot once invisible
if (progress == 1) dot.remove()
},
5000 // 5 second duration
)
// playing the animation
dot.playAnim(fadingAnimation)
Example use 2:
- Making a dot smoothly move back and forth 200px, while blinking, every second, for eternity
let distance = 200, ix = dot.x
dot.playAnim(new Anim((progress, playCount, deltaTime)=>{
// fading the dot over 1 second
dot.a = 1 - progress
// moving the dot left or right depending on if the animation play count is pair
dot.x = ix + ((playCount%2)||-1) * distance * progress
if (progress==1) ix = dot.x
}, -1000)) // repeats every 1 seconds because of the "-"
Input Devices
TypingDevice
The TypingDevice class is automatically created and accessible by any Canvas instance. It provides information about the user's keyboard, such as the current keys pressed.
Note: for setting the keyup and keydown event listeners, use the prebuilt functions from the Canvas class.
The TypingDevice's main attributes are:
- keysPressed -> The keys which are currently pressed.
To set the keys event listeners, use the following prebuilt functions:
- setKeyDown(cb, global), setKeyUp(cb, global)
// Setting the keydown listener
CVS.setKeyDown()
// Setting the keyup listener with a custom callback (typingDevice, event)=>
CVS.setKeyUp((typingDevice, e)=>{
console.log("Custom callback: ", typingDevice, e)
})
// OR
// Setting both listeners globally. (This will detect the key inputs even when the canvas is not directly focused)
CVS.setKeyDown(null, true)
CVS.setKeyUp((typingDevice, e)=>{
console.log("Custom callback: ", typingDevice, e)
}, true)
To get whether a certain key is down, use the isDown() function:
- isDown(key)
// Accessing the typing device's "a" key state
const isKey_A_down = CVS.typingDevice.isDown("a")
console.log("Is the 'a' key down: ", isKey_A_down)
Mouse
The Mouse class is automatically created and accessible by any Canvas instance. It provides information about the user's mouse, such a position, speed, direction, and buttons pressed.
Note: for setting the move, leave, up, and down mouse event listeners, use the prebuilt functions from the Canvas class.
The Mouse's main attributes are:
- valid -> Whether the mouse pos is valid(is inside the canvas and initialized).
- x -> The current x value of the mouse on the canvas.
- y -> The current y value of the mouse on the canvas.
- dir -> The direction in degrees of the mouse's last movement.
- speed -> The current speed (in px/s) of the mouse.
- clicked -> Whether the main button of the mouse is active.
- rightClicked -> Whether the secondary button of the mouse is active.
- scrollClicked -> Whether the scroll button of the mouse is active (pressed).
- extraForwardClicked -> Whether the extra forward button of the mouse is active (not present on every mouse).
- extraBackClicked -> Whether the extra back button of the mouse is active (not present on every mouse).
- holdValue -> A custom value to set manually. Ex: can be used to reference an object the mouse is holding more easily
To set the mouse event listeners, use the following prebuilt functions:
- setMouseMove(cb, global), setMouseLeave(cb, global), setMouseDown(cb, global), setMouseUp(cb, global)
// Setting the mousemove listener
CVS.setMouseMove()
// Setting the mousedown listener with a custom callback (mouse, event)=>
CVS.setMouseDown((mouse, e)=>{
console.log("Custom callback: ", mouse, e)
})
// OR
// Setting listeners globally. (This will detect the mouse inputs even if the mouse is not directly interacting with the canvas)
CVS.setMouseMove(null, true)
CVS.setMouseDown((mouse, e)=>{
console.log("Custom callback: ", mouse, e)
}, true)
CVS.setMouseUp((mouse, e)=>{
console.log("Other custom callback: ", mouse, e)
}, true)
Example use 1:
- Making a dot throwable, and changing its color on mouse hover and click
// Using the getDraggableDotCB utility function to get a dragCallback
const dragCallback = CanvasUtils.getDraggableDotCB()
// Creating a mostly default shape, with a single dot
const throwableDot = new Shape([10, 10], new Dot([10, 10]), null, null, null,
(render, dot, ratio, setupResults, mouse, dist, shape)=>{// drawEffectCB callback
// Changing the dot's size based on mouse distance for an additional small effect
dot.radius = CDEUtils.mod(shape.radius*2, ratio, shape.radius*2*0.5)
// Checking if the mouse is hovering the dot
const isMouseOver = dot.isWithin(m.pos, true)
// if the mouse is over and clicked, set the dot's color to red
if (isMouseOver && m.clicked) {
dot.color = [255, 0, 0, 1]
}
// if the mouse is only over, set the dot's color to green
else if (isMouseOver) {
dot.color = [0, 255, 0, 1]
}
// if the mouse is neither over nor clicked, set the dot's color to white
else {
dot.color = [255, 255, 255, 1]
}
// Calling the dragCallback to make the dragging and throwing effect
dragCallback(shape.dots[0], m, dist, ratio)
}
)
// Adding the shape
CVS.add(throwableDot)
Utilities
CanvasUtils
The CanvasUtils class provides generic functions for common effects.
DrawOuterRing
This function is used to draw a ring around a dot.
drawOuterRing(dot, color, radiusMultiplier, forceBatching)
// (Running in the drawEffectCB() function of some shape...)
{
...
// Draws a ring around the dot, 3x bigger than the dot's radius and of the same color
CanvasUtils.drawOuterRing(dot, dot.colorObject, 3)
}
RotateGradient
This function is used to rotate the gradient of an object.
rotateGradient(obj, duration=1000, speed=1, isFillColor=false)
// (Running in dummyFilledShape' setupCB)
// rotateGradient should only be called once, as it starts an infinite animation
{
...
// Rotates the shape's gradient filling. Completes a revolution in 3 seconds
CanvasUtils.rotateGradient(dummyFilledShape, 3000, 1, true)
}
getDraggableDotCB
This function is used to make a dot throwable.
getDraggableDotCB(pickableRadius=50)
// getDraggableDotCB should only be called once, but it returns a callback that needs to be called every frame
// (Running in of some js file)
// Provides the callback to make the dot throwable.
const dragAnimCallback = CanvasUtils.getDraggableDotCB()
...
// (Running in the drawEffectCB() function of some shape...)
{
...
// Only for the first dot of the shape
if (shape.firstDot.id == dot.id) {
// Makes the dot pickable in a 100px radius, and throwable using default CDE physics (works best on higher refresh rates)
dragAnimCallback(dot, mouse, dist, ratio, 100)
}
}
getTrailEffectCB
This function is used to make an object have a customizable trail effect when moving.
getTrailEffectCB(canvas, obj, length=8, moveEffectCB=null, disableDefaultMovements=false)
// getTrailEffectCB should only be called once, but it returns a callback that needs to be called every frame
// Here we create a shape with a single dot receiving the trail effect.
const trailingDot = new Shape([200, 100], new Dot(), null, "lime", null,
(render, dot, ratio, [dragCB, trailCB], mouse, dist, shape)=>{// drawEffectCB
// Make sure the effect is only running on the desired dot (Optional in this example, since there is only one dot)
if (dot.id==shape.firstDot.id) {
dragCB(shape.firstDot, mouse, dist, ratio) // see previous example for details
trailCB(mouse) // call the trail effect callback every frame and give it the mouse in parameter
}
}, null, (shape)=>{// setupCB
// Here we get/initialize both effect callbacks in an array to be reused in the drawEffectCB
return [
CanvasUtils.getDraggableDotCB(), // see previous example for details
// Creating a trail effect based on the shape's first dot, with a length of 10
CanvasUtils.getTrailEffectCB(CVS, shape.firstDot, 10, (dot, ratio, isMoving, mouse)=>{// moveEffectCB
// This callback is called every frame
// "ratio" is the index of the object in the trail divided by the length of the trail
// "isMoving" represents whether the object is currently moving or not
//While the user is dragging/moving the dot, every trail object has a set value
if (isMoving && mouse.clicked) {
dot.a = ratio
dot.radius = 25*(1-ratio)
}
// otherwise, the trail objects progressively lose visibility
dot.a -= (1-ratio)/1000
dot.radius = 25*ratio
})
]
})
// Add the shape to the canvas
CVS.add(trailingDot)
drawLine
This function is used to draw a connection between a Dot and another pos/object.
drawLine(dot, target, renderStyles, radiusPaddingMultiplier=0, lineType, spread, forceBatching)
// (Running in the drawEffectCB() function of some shape...)
{
...
// Only if the distance with the ratioPos is lower than the shape's limit
if (dist < shape.limit) {
// Draws a line between the dot and the dot's ratioPos, adjusting the opacity of the line via the distance ratio
CanvasUtils.drawLine(
dot, // start Dot
[200, 200], // end position (can also be a Dot)
render.profile.update(
Color.rgba(dot.r,dot.g,dot.b,CDEUtils.mod(0.5, ratio)) // updates only the color, but uses every previously set styles
)
)
}
}
drawDotConnections
This function is used to draw the connections between a Dot and the ones in its connections
attribute. (Especially useful when using a Grid!)
drawDotConnections(dot, renderStyles, radiusPaddingMultiplier=0, lineType, spread, forceBatching)
// (Running in the drawEffectCB() function of some shape...)
{
...
// Draws lines between the dot and its connections, using the shape's color, and with a start padding of 2.5x the radius
CanvasUtils.drawDotConnections(dot, shape.colorObject, 2.5)
}
Note: Functions in this class only accept RGBA arrays or a Color instance for the color parameter.
Generic follow paths
The object FOLLOW_PATHS provides generic follow paths.
Infinity Sign
Provides a sideways "8"-like follow path.
INFINITY_SIGN(width, height, progressOffset)
Circle
Provides a circular follow path.
CIRCLE(width, height, progressOffset)
Rectangle
Provides a rectangular follow path.
RECTANGLE(width, height, progressOffset)
Quadratic
Provides a vertical quadratic curve follow path.
QUADRATIC(width, height, isFliped)
Linear
Provides a linear follow path.
LINEAR(width, a)
Sine Wave
Provides a sine wave-like follow path.
SINE_WAVE(width, height)
Cosine Wave
Similar to Sine Wave, provides a cosine wave-like follow path.
COSINE_WAVE(width, height)
Relative
Doesn't move the dot, unless provided a x/y value. Also accepts other generic follow paths as x/y values.
RELATIVE(forceX?, forceY?)
Example use 1:
- Make a dot follow a circle of 200px radius, over 5 seconds
dummyShape.dots[0].follow(5000, Anim.linear, null, CanvasUtils.FOLLOW_PATHS.CIRCLE(400, 400))
Example use 2:
- Drawing a sine wave graph
const manualSineWaveDrawer = new Shape([100, 100], [
new Dot(), // adding a blank dot
new Dot([10, 0],null,null,null,(dot, shape)=>shape.firstDot) // adding a dot with the first dot as its anchorPos
], null, null, 100, (render, dot, ratio)=>{// shape's drawEffectCB
// drawing a dotted red line between the two dots
CanvasUtils.drawDotConnections(dot, render.profile1.update([255,0,0,1], null, null, null, [5]))
// simple radius hover effect
dot.radius = CDEUtils.mod(_Obj.DEFAULT_RADIUS*2, ratio, _Obj.DEFAULT_RADIUS*2*0.8)
}, null, (shape)=>{// Shape's setupCB
// making the first dot follow a circle path over 5 seconds
shape.firstDot.follow(5000, null, (prog, dot)=>{
shape.add(new Dot(null, 4, null, null, dot.pos)) // creating a new dot each frame at the dot's pos
}, CanvasUtils.FOLLOW_PATHS.CIRCLE(null, null, 0.5))
// making the second dot follow a linear path for its X value and not providing any forced Y value (The Y value will be changed via the anchorPos).
shape.dots[1].follow(5000, Anim.LINEAR, (prog, dot)=>{
shape.add(new Dot(null, 2, "red", null, dot.pos)) // creating a new dot each frame at the dot's pos
}, CanvasUtils.FOLLOW_PATHS.RELATIVE(CanvasUtils.FOLLOW_PATHS.LINEAR(800, 0)))
// adding a connection between the first and second dot
shape.firstDot.addConnection(shape.dots[1])
}, null, null, true)
//Adding the shape to the canvas
CVS.add(manualSineWaveDrawer)
CDEUtils
The CDEUtils class provides utilities such as random
, clamp
, FPSCounter
, and mod
.
Linear interpolation (mod)
This function is used to get a value in a range.
mod(max, ratio, range)
// (Running in the drawEffectCB() function of some shape...)
{
...
// It is recommended to always put the ratio provided by drawEffectCB() as the second parameter of mod.
// The dot opacity will go from 1(max) to 0.2(min) depending on the value of the ratio
dot.a = CDEUtils.mod(1, ratio, 0.8)
}
random
This function is used to return a random value. (decimal or whole)
random(min, max, decimals=0)
clamp
This function is used to clamp a value within certain bounds.
clamp(value, min=Infinity, max=Infinity)
getDist
This function is used to return the Pythagorean distance between two positions.
getDist(x1, y1, x2, y2)
getMinMax
This function is used to return the biggest and lowest values within an array. (propPath can be used to specify a property path to access the values to compare if the array contains objects)
getMinMax(arr, propPath=null)
repeatedTimeout
This function is used to run a callback for a specific amount of time.
repeatedTimeout(iterationCount, callback, delay=5)
FPSCounter
The FPSCounter class allows to get the live frame per second value of a running loop.
Example use:
- Displaying the canvas FPS
// Creating a FPSCounter instance
const fpsCounter = new FPSCounter()
// Creating a Canvas
const CVS = new Canvas(canvas, ()=>{//loopingCB
// Get the fps value (needs to be run each frame)
const fpsValue = fpsCounter.getFps()
// Displaying the live FPS
document.getElementById("fps_display").textContent = fpsValue+" fps"
})
Execution Order
Level 1: Static setup
This is mostly the declaration state, nothing is fully created / usable just yet.
- Canvas instance creation
- Initial canvas object creation (shapes, dots, ...)
- Adding the initial canvas objects to the canvas
- Settings events listeners
- Starting the main loop
Level 2: Adding canvas objects to the canvas
Once everything is declared, objects will start getting added to the canvas.
- Sets the parent attribute on references and definitions
- Runs the
initialize()
function on both references and definitions (On every added object) - Adds objects as references or definitions in the canvas
Level 3: Root canvas object initialization
At this point all references and definitions are initialized. By initializing a reference, we also initialize its children (ex: dots).
- Creates / adds all of the shapes' dots and sets some of their attributes
- Runs the
initialize()
function for each dot contained in the shape. (After getting itsinitialize()
function called, the shape calls theinitialize()
of all its dots, while also setting some of their attributes.)
Runs the following on applicable objects:
- if
initPos
is a callback ->initPos(canvas, this)
- if
initDots
is a string ->createFromString(initDots)
- if
initDots
is a callback ->initDots(this, canvas)
- if
initRadius
is a callback ->initRadius(this)
- if
initColor
is a callback ->initColor(render, this)
setupCB(this, parent)
- if a FilledShape and
fillColor
is a callback ->initFillColor(render, this)
- Adjusts the
pos
according to theanchorPos
- Sets the
initialized
attribute totrue
for shapes
Level 4: Children's initialization
After this, every dot will be initialized, and all canvas objects will be ready to be drawn.
Runs the following on referenced dots (dots in shapes):
- if
initPos
is a callback ->initPos(parent, this)
- if
initRadius
is a callback ->initRadius(parent, this)
- if
initColor
is a callback ->initColor(render, this)
setupCB(this, parent)
- Adjusts the
pos
according to theanchorPos
Level 5: Drawing stage
This is where the canvas objects will appear on the canvas. Starts when calling startLoop()
on the Canvas instance. The draw
function runs every frame for every canvas object. The following will happen:
- DeltaTime becomes available via the Canvas instance
- Updates the object's
pos
according to theanchorPos
- Plays the object's animations, if any
- Sets the
initialized
attribute totrue
for dots after 1 frame - Runs the
drawEffectCB
for dots - Runs the
loopCB
for all objects - Visually draws the dots
- Updates the
ratioPos
for shapes ifratioPos
is a function - Draws the fill area of filled shapes
Notes:
Reference: an object containing other objects. (ex: Shape, FilledShape, Grid)
Definition: a standalone object. (ex: Dot without a parent Shape, TextDisplay, ImageDisplay)
When accessing a shape's dots within the setupCB call, dots will have their initialized attribute set to false, but will still be mostly fully initialized, and thus, usable. (Only applicable for dots)
Optimization
To keep the best performance in your website/app, you may want to use some optimization techniques. Here are some tips you can use to optimize your effects.
- Using a static canvas:
Using a static canvas is a lot more performant when working with stuff that doesn't need to be updated often.
Static canvas example:
// Creating a static canvas uses the same declaration as a classic canvas
const staticCVS = new Canvas(canvasElement, ()=>{/*Custom callback that gets call each frame*/})
// ** Here you would declare and add all the shapes and objects **
// Here is a simple shape for example
const dummyShape = new Shape([100, 100],[new Dot([-50, -50]),new Dot([-50, 50]),new Dot([50, -50]),new Dot([50, 50]),])
staticCVS.add(dummyShape)
// Once declarations are done, initialize the canvas as static
staticCVS.initializeStatic()
Updating a static canvas:
// (Considering the example above ↑)
// You can use the following:
// Draw a single frame
staticCVS.drawStatic()
// Clear the canvas
staticCVS.clear()
// Clear the canvas and draw a frame
staticCVS.cleanDrawStatic()
Notes:
- Since there is no drawing loop, you do not need to run
CVS.startLoop()
- You can also overlay dynamic and static canvases to seamlessly distribute the drawing process.
- Optimize repetitive operations:
Callbacks such as drawEffectCB
can sometimes run thousands of times per second, so make sure these are as optimized as possible.
Optimization examples for drawEffectCB
:
/*
In this example, we compare two drawEffectCB that both do the same thing,
one version is optimized while the other isn't.
*/
const badDrawEffectCB = (render, dot, ratio, dragAnim, mouse, dist, shape)=>{
// Logging the current dot to the console
console.log(dot)
// Dynamically updating the current dot's alpha
dot.a = CDEUtils.mod(1, ratio, 0.6)
// Drawing a line between the current dot and its ratioPos
CanvasUtils.drawLine(dot, dot.ratioPos, render.profile1.update(Color.rgba(0,255,255,CDEUtils.mod(1, ratio, 0.8))))
// Running the draggableCB to make ONLY the first dot draggable (here dragAnim comes from the setupCB)
dragAnim(shape.dots[0], mouse, dist, ratio)
// Updating the first dot's color on mouse hover/click
const mouseOn = shape.dots[0].isWithin(mouse.pos, true)
if (mouseOn && mouse.clicked) shape.dots[0].color = [255, 0, 0, 1]
else if (mouseOn) shape.dots[0].color = [0, 254, 0, 1]
else shape.dots[0].color = [255, 255, 255, 1]
}
const optimizedDrawEffectCB = (render, dot, ratio, dragAnim, mouse, dist, shape, isActive)=>{
// Never leave console logs in drawEffectCb for production as these cause ABSOLUTELY HORENDOUS LAG, especially when the console is opened.
// DONT DO THIS -> console.log(dot)
// This was pretty much already optimized
dot.a = CDEUtils.mod(1, ratio, 0.6)
// Here, since the alpha is going to be 0, we can prevent drawing when the lines are not visible anyway (outside the shape's limit)
if (dist < shape.limit) CanvasUtils.drawLine(dot, dot.ratioPos, render.profile1.update([0,255,255,CDEUtils.mod(1, ratio, 0.8)]))
// We can store the firstDot into a variable to avoid the object lookups each time
const firstDot = shape.dots[0]
// Since only the first dot is made draggable, it is better to avoid calling the dragAnim for each dot
if (firstDot.id == dot.id) {
dragAnim(firstDot, mouse, dist, ratio)
// Again, the effect is only for the first dot
// Here we use the provided "isActive" variable to make sure the mouse checks are only running when necessary
if (isActive) {// (isActive is only true if ratio < 1)
const mouseOn = firstDot.isWithin(mouse.pos, true)
if (mouseOn && mouse.clicked) firstDot.color = [255, 0, 0, 1]
else if (mouseOn) firstDot.color = [0, 254, 0, 1]
else firstDot.color = [255, 255, 255, 1]
}
}
}
Intended Practices
Putting
null
as any parameter value will assign it the default value.Putting
undefined
as any parameter value will override the default behavior if applicable.Use the
mod()
function for effective ratio usages.If needed and applicable, use the available prebuilt event listeners.
More complex shapes can have very extensive declarations, declare them in a separate file(s) and use them in a centralized project file.
React Component Template
Here is the proposed CDECanvas React component. Run npx cdejs-react
or create a CDECanvas.jsx file and copy and paste the code below.
import {forwardRef, useEffect, useImperativeHandle, useRef} from "react"
import {Canvas, CDEUtils} from "cdejs"
/**
* HOW TO USE:
*
* 1. Add the <CDECanvas/> component at the root of your target element.
* 2. If necessary, create a ref and link it to your <CDECanvas ref={*yourRef*}/> component to access some utility functions of the canvas. (See the imperativeHandle bellow)
* 3. Create your declarations and interactions and build cool effects!
*
* PARAMETERS:
* - declarations -> A callback containing the setup/declaration of all canvas obj and if applicable, adding them to the canvas. (CVS)=>{...}
* - interactions -> A callback containing the desired built-in input device listeners. (CVS)=>{...}
* - isStatic -> If true, initializes the canvas as static.
* - loopingCB, fpsLimit, visibilityChangeCB, cvsFrame, settings, willReadFrequently -> see https://github.com/Louis-CharlesBiron/canvasDotEffect?#canvas
*/
export const CDECanvas = forwardRef(({declarations, interactions, isStatic, loopingCB, fpsLimit, visibilityChangeCB, cvsFrame, settings, willReadFrequently}, ref)=>{
const htmlElementCanvasRef = useRef(null), cvsInstanceRef = useRef(null)
// Utility canvas functions
useImperativeHandle(ref, ()=>({
getCVS:()=>cvsInstanceRef.current,
adjustSize:()=>cvsInstanceRef.current.setSize()
}))
useEffect(()=>{
const CVS = new Canvas(htmlElementCanvasRef.current, loopingCB, fpsLimit, visibilityChangeCB, cvsFrame, settings, willReadFrequently)
cvsInstanceRef.current = CVS
// Setup canvas objects and listeners
if (CDEUtils.isFunction(declarations)) declarations(CVS)
if (CDEUtils.isFunction(interactions)) interactions(CVS)
// Start
if (isStatic) CVS.initializeStatic()
else CVS.startLoop()
// On unmount
return ()=>CVS.stopLoop()
}, [])
return <canvas ref={htmlElementCanvasRef}></canvas>
})
Credits
Made by Louis-Charles Biron !