Web Canvas
Canvas Basic Usage
- Path2D 对象.
- 绘制路径:
beginPath()->draw()->closePath(). - 绘制样式: 颜色/渐变/变换/阴影.
- 绘制图形:
fill/stroke/clip. - 绘制文字:
font/fillText()/measureText().
const context = canvas.getContext('2d')
// 根据参数画线
function drawLine(fromX, fromY, toX, toY) {
context.moveTo(fromX, fromY)
context.lineTo(toX, toY)
context.stroke()
}
// 根据参数画圆
function drawCircle(x, y, radius, color) {
context.fillStyle = color
context.beginPath()
context.arc(x, y, radius, 0, Math.PI * 2, true)
context.closePath()
context.fill()
context.stroke()
}
// 改变 canvas 中图形颜色
function changeColor(color) {
context.fillStyle = color
context.fill()
}
<canvas width="600" height="300"></canvas>
<script>
const cx = document.querySelector('canvas').getContext('2d')
function branch(length, angle, scale) {
cx.fillRect(0, 0, 1, length)
if (length < 8) return
cx.save()
cx.translate(0, length)
cx.rotate(-angle)
branch(length * scale, angle, scale)
cx.rotate(2 * angle)
branch(length * scale, angle, scale)
cx.restore()
}
cx.translate(300, 0)
branch(60, 0.5, 0.8)
</script>
Canvas Game Loop
For all objects:
- constructor:
position{x, y},speed{x, y},size{x, y} - update(deltaTime): change position or speed
- draw(ctx): use canvas api and object properties (position/size) to render objects
const canvas = document.getElementById('gameScreen')
const ctx = canvas.getContext('2d')
const GAME_WIDTH = 800
const GAME_HEIGHT = 600
const game = new Game(GAME_WIDTH, GAME_HEIGHT)
let lastTime = 0
function gameLoop(timestamp) {
const deltaTime = timestamp - lastTime
lastTime = timestamp
ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT)
game.update(deltaTime)
game.draw(ctx)
requestAnimationFrame(gameLoop)
}
requestAnimationFrame(gameLoop)
Canvas Performance
Canvas buffer:
frontCanvasContext.drawImage(bufferCanvas, 0, 0)
- multiple canvas: top layer, background layer, interactive layer
- disable alpha path
const ctx = canvas.getContext('2d', { alpha: false })
Offscreen canvas and web workers:
transferControlToOffscreen method:
// index.js
const offscreenCanvas = document.querySelector('#frame2')
const offscreen = offscreenCanvas.transferControlToOffscreen()
const worker = new Worker('./worker.js')
worker.postMessage({ canvas: offscreen }, [offscreen])
// worker.js
onmessage = function (event) {
canvas = event.data.canvas
context = canvas.getContext('2d')
}
OffscreenCanvas in service workers:
// Create reusable canvas
const offscreen = new OffscreenCanvas(512, 512)
const context = offscreen.getContext('2d')
// eslint-disable-next-line no-restricted-globals -- Service worker.
self.addEventListener('fetch', (ev) => {
// Only handle image requests
const url = new URL(ev.request.url)
if (url.pathname.startsWith('/artwork/'))
ev.respondWith(artwork(ev))
})
async function artwork(ev) {
const cache = await caches.open('artwork')
// 1. Return from cache
let response = await cache.match(ev.request)
if (response)
return response
// 2. Or fetch from network
response = await fetch(ev.request)
if (!response.ok || response.status !== 200)
return response
// 3. Use canvas to resize
let blob = await response.blob()
context.drawImage(await createImageBitmap(blob), 0, 0, 512, 512)
blob = await offscreen.convertToBlob({ type: 'image/png' })
// 4. Create new headers
const headers = new Headers(response.headers)
headers.set('content-type', blob.type)
headers.set('content-length', blob.size)
// 5. Create new response
const { status, statusText } = response
response = new Response(await blob.arrayBuffer(), {
status,
statusText,
headers
})
await cache.put(ev.request, response.clone())
return response
}
CSS Houdini Painting API
// checkerboard.js:
// Create a `PaintWorklet`.
class CheckerboardPainter {
static get contextOptions() {
return { alpha: true }
}
/**
* @returns {string[]} any custom properties or regular properties
*/
static get inputProperties() {
return ['--red', '--green', '--blue', '--width', 'height']
}
paint(context, geometry, props) {
const colors = [
props.get('--red').toString(),
props.get('--green').toString(),
props.get('--blue').toString(),
]
const size = Number.parseInt(props.get('--width'))
for (let y = 0; y < geometry.height / size; y++) {
for (let x = 0; x < geometry.width / size; x++) {
const color = colors[(x + y) % colors.length]
context.beginPath()
context.fillStyle = color
context.rect(x * size, y * size, size, size)
context.fill()
}
}
}
}
// Register our class under a specific name
registerPaint('checkerboard', CheckerboardPainter)
<!-- Load a `PaintWorklet`. -->
<script>
if ('paintWorklet' in CSS) {
CSS.paintWorklet.addModule('checkerboard.js')
}
</script>
<!-- Use a `PaintWorklet`. -->
<style>
textarea {
background-image: paint(checkerboard);
}
</style>
<textarea></textarea>