lab008: Enter the Canvas
Aug 1, 2022
Ah yes, the famous Matrix screen saver effect. The full screen effect is done using CSS’s absolute positioning and vueUse’s useWindowSize utility, which provides the reactive width and height of the browser window which we can send in to the <canvas>
element to tell it to cover the entire window.
I had a bear of a time troubleshooting how to disable vertical scrolling while in the screen saver effect, but in the end it was vanilla JS that worked well for me: document.body.style = 'overflow-y: hidden'
to hide the vertical scrollbar and document.body.style = 'overflow-y: auto'
to return the vertical scrollbar back to normal.
UX edge case: If users hit the back button while in “screen saver mode”, they will find that the vertical scroll bars have disappeared and they are unable to navigate vertically. Solution: Use an
onUnmounted
function along withdocument.body.style = 'overflow-y: auto'
to ensure vertical scroll bar behavior remains normal for the rest of the website. I also cleared mysetInterval
function and removed the window resize event listener inonUnmounted
to prevent memory leaks.
You can check out the Matrix digital rain effect code, which is both clever and concise. There are other implementations out there as well.
The pink rectangle you see above is the background of a <canvas>
HTML element. This “canvas” provides you with a context
for either 2D or WebGL (WebGL allows 3D). The context holds all sorts of properties that can be used to make anything from animations to interactive experiences and games.
Notice that the code snippet below is wrapped within an onMounted
function–a Vue 3 lifecycle hook that fires after the .vue component has been mounted to the DOM. Without this hook, any template refs will error out because there is no DOM element to connect with.
We then utilize the context, ctx
, provided by the canvas API to create the looping green square animation as well as the row of blue squares.
// template ref--canvas element must have id="myCanvas"
const myCanvas = ref(null)
// onMounted lifecycle hook
onMounted(() => {
// after mount, it's safe to work with myCanvas--not before!
myCanvas.value.style.background = 'violet'
// get the appropriate context, '2d' or 'webgl'
const ctx = myCanvas.value.getContext('2d')
ctx.fillStyle = 'blue'
// creates the row of blue squares
for (let i = 1; i <= 10; i++) {
alpha.value = i * 0.1
ctx.globalAlpha = alpha.value
ctx.fillRect(i * 50, 20, 40, 40)
}
ctx.fillStyle = 'green'
ctx.fillRect(100, 100, 100, 100)
// function to create the green square fading in and out
const fadeOut = () => {
// notice the recursive loop -- this is the animation loop, using the built-in function `requestAnimationFrame`
requestAnimationFrame(fadeOut)
ctx.clearRect(100, 100, myCanvas.value.width, myCanvas.value.height)
ctx.globalAlpha = Math.sin(alpha.value)
ctx.fillRect(100, 100, 100, 100)
alpha.value += -0.05
}
fadeOut()
})
// template ref--canvas element must have id="myCanvas"
const myCanvas = ref(null)
// onMounted lifecycle hook
onMounted(() => {
// after mount, it's safe to work with myCanvas--not before!
myCanvas.value.style.background = 'violet'
// get the appropriate context, '2d' or 'webgl'
const ctx = myCanvas.value.getContext('2d')
ctx.fillStyle = 'blue'
// creates the row of blue squares
for (let i = 1; i <= 10; i++) {
alpha.value = i * 0.1
ctx.globalAlpha = alpha.value
ctx.fillRect(i * 50, 20, 40, 40)
}
ctx.fillStyle = 'green'
ctx.fillRect(100, 100, 100, 100)
// function to create the green square fading in and out
const fadeOut = () => {
// notice the recursive loop -- this is the animation loop, using the built-in function `requestAnimationFrame`
requestAnimationFrame(fadeOut)
ctx.clearRect(100, 100, myCanvas.value.width, myCanvas.value.height)
ctx.globalAlpha = Math.sin(alpha.value)
ctx.fillRect(100, 100, 100, 100)
alpha.value += -0.05
}
fadeOut()
})
Tip: when working with 3rd-party libraries that require template refs (connecting to the DOM the “Vue way” as opposed to vanilla JS DOM manipulation), be aware that you’ll have to do the
onMounted
dance and wrangle any scoping issues that may come up when you’re trying to access something that’s insideonMounted
and is therefore not accessible from the template.
Click to animate this div
The element animated above was not a <canvas>
element–it is just a plain old <div>
. You can use requestAnimationFrame
to animate an element’s CSS properties. I find this fascinating–there are multiple approaches to animation on the web explore and evaluate.
Be aware of continously running animation/game loops since they can take up a lot of resources. The code snippet below uses an
if
check to turn off the animation loop after 2 seconds.
// template ref
const myDiv = ref(null)
// boolean flag
const isDone = ref(false)
// init
let start, previousTimeStamp
let scale = 1
// call this function with requestAnimationFrame
function step(timestamp) {
if (start === undefined)
start = timestamp
const elapsed = timestamp - start
if (previousTimeStamp !== timestamp) {
const count = Math.min(0.1 * elapsed, 300)
scale += 0.005
scale = Math.min(scale, 2)
myDiv.value.style.transform = `translateX(${count}px) scale(${scale})`
if (count === 300)
isDone.value = true
}
if (elapsed < 2000) {
previousTimeStamp = timestamp
if (!isDone.value)
window.requestAnimationFrame(step)
}
}
const animateDiv = () => window.requestAnimationFrame(step)
// template ref
const myDiv = ref(null)
// boolean flag
const isDone = ref(false)
// init
let start, previousTimeStamp
let scale = 1
// call this function with requestAnimationFrame
function step(timestamp) {
if (start === undefined)
start = timestamp
const elapsed = timestamp - start
if (previousTimeStamp !== timestamp) {
const count = Math.min(0.1 * elapsed, 300)
scale += 0.005
scale = Math.min(scale, 2)
myDiv.value.style.transform = `translateX(${count}px) scale(${scale})`
if (count === 300)
isDone.value = true
}
if (elapsed < 2000) {
previousTimeStamp = timestamp
if (!isDone.value)
window.requestAnimationFrame(step)
}
}
const animateDiv = () => window.requestAnimationFrame(step)
Between .svg and Lottie animations, the <canvas>
2d and webgl APIs, CSS animation, and requestAnimationFrame, animation on the web is multi-faceted and diverse.
I will be continuing my expedition into canvas 2d, which serves as a foundation on top of which many excellent 3rd-party libraries have been written, including pixi.js (2D WebGL renderer), matter.js (2D physics engine), Phaser (2D game engine), and Three.js (3D library). Until then, DannyDevs out!