Espace de pixels permettant de dessiner à l'aide d'une multitude de méthodes javascript
<canvas width="800" height="600"></canvas>
const canvas = document.querySelector('canvas')
const context = canvas.getContext('2d')
Ce code ne sera pas répété dans les exemples qui suivent
Pour dessiner, la méthode classique consiste à
Lignes
context.beginPath()
context.moveTo(50, 50)
context.lineTo(200, 200)
context.lineTo(50, 200)
context.closePath()
context.stroke()
Lignes
context.beginPath()
context.moveTo(50, 50)
context.lineTo(200, 200)
context.lineTo(50, 200)
context.fill()
Remplissages
Il existe de nombreuses propriétés pour changer le style du dessin
Cela peut concerner les lignes ou le remplissage
Le style s'appliquera sur tous les dessins suivant le changement de propriété
Modifier le style de la ligne
context.beginPath()
context.moveTo(50, 50)
context.lineTo(200, 200)
context.lineTo(50, 200)
context.lineWidth = 20
context.lineCap = 'round' // round | butt | square
context.lineJoin = 'bevel' // bevel | round | mitter
context.strokeStyle = 'orange'
context.stroke()
Modifier le style de la ligne
Modifier le style de remplissage
context.beginPath()
context.moveTo(50, 50)
context.lineTo(200, 200)
context.lineTo(50, 200)
context.fillStyle = 'rgba(255, 0, 0, 0.5)'
context.fill()
Modifier le style de remplissage
Ombres
context.beginPath()
context.moveTo(50, 50)
context.lineTo(200, 200)
context.lineTo(50, 200)
context.fillStyle = 'rgba(255, 0, 0, 1)'
context.shadowColor = 'blue'
context.shadowBlur = 50
context.shadowOffsetX = 5
context.shadowOffsetY = 10
context.fill()
Ombres
Certaines méthodes permettent de définir des tracés autre que des lignes
rect() permet de tracer un rectangle
arc() permet de tracer un arc de cercle
La variable globale Math possède plusieurs propriétés et méthodes mathématiques
Celle que nous allons utilisé est Math.PI et permet d'obtenir le nombre π utile pour dessiner des arcs de cercle
context.fillStyle = 'orange'
context.strokeStyle = 'orange'
context.beginPath()
context.rect(50, 50, 200, 100)
context.fill()
context.beginPath()
context.arc(400, 50, 100, 0, Math.PI, false)
context.fill()
context.beginPath()
context.fillStyle = 'orange'
context.rect(50, 200, 200, 100)
context.stroke()
context.beginPath()
context.arc(400, 200, 100, 0, Math.PI, false)
context.stroke()
fillRect() permet de remplir un rectangle sans passer par beginPath() et fill()
clearRect() permet d'effacer un rectangle
context.fillStyle = 'orange'
context.fillRect(50, 50, 300, 160)
context.clearRect(50, 50, 100, 80)
context.fillStyle = 'cyan'
context.fillRect(160, 60, 20, 70)
context.beginPath()
context.fillStyle = 'black'
context.arc(280, 210, 50, 0, Math.PI, false)
context.arc(120, 210, 50, 0, Math.PI, false)
context.fill()
const text = 'Lorem ipsum dolor sit amet'
context.font = '40px Arial'
context.textAlign = 'center' // left | center | right
context.textBaseline = 'top' // top | bottom | middle | alphabetic | hanging
console.log(context.measureText(text).width)
context.fillText(text, 300, 100)
context.strokeText(text, 300, 160)
Pour dessiner une image, il est nécessaire de l'avoir chargée
Il faut donc créer, en javascript, un objet Image et écouter son événement load
const image = document.createElement('img')
image.addEventListener('load', () =>
{
context.drawImage(image, 0, 0)
context.drawImage(image, 0, 0, image.width * 0.5, image.height * 0.5)
})
image.src = 'image.jpg'
Dégradé linéaire
const gradient = context.createLinearGradient(50, 50, 250, 250) // x1, y1, x2, y2
gradient.addColorStop(0, 'rgb(255, 80, 0)')
gradient.addColorStop(0.5, 'rgb(255, 191, 0)')
gradient.addColorStop(1, 'rgb(255, 246, 155)')
context.fillStyle = gradient
context.fillRect(0, 0, 400, 400)
Dégradé linéaire
Dégradé Radial
const gradient = context.createRadialGradient(
100, 100, 50, // x1, y1, r1
100, 250, 250 // x2, y2, r2
)
gradient.addColorStop(0, 'rgb(255, 80, 0)')
gradient.addColorStop(0.5, 'rgb(255, 191, 0)')
gradient.addColorStop(1, 'rgb(255, 246, 155)')
context.fillStyle = gradient
context.fillRect(0, 0, 400, 400)
Dégradé Radial
Les fonctions save() et restore() permettent de sauvegarder l'état du style et de le restaurer
Cela équivaut à un historique de pinceau
Ces méthodes ne permettent pas de sauvegarder ou restaurer l'état du canvas !
context.save()
context.beginPath()
context.moveTo(50, 50)
context.lineTo(50, 100)
context.lineWidth = 1
context.stroke()
context.save()
context.beginPath()
context.moveTo(100, 50)
context.lineTo(100, 100)
context.lineWidth = 5
context.stroke()
context.save()
context.beginPath()
context.moveTo(150, 50)
context.lineTo(150, 100)
context.lineWidth = 10
context.stroke()
context.restore()
context.beginPath()
context.moveTo(200, 50)
context.lineTo(200, 100)
context.stroke()
context.restore()
context.beginPath()
context.moveTo(250, 50)
context.lineTo(250, 100)
context.stroke()
Les méthodes bezierCurveTo() et quadraticCurveTo() permettent de dessiner des courbes de bézier en spécifiant chacun des points
Courbe de bézier
context.beginPath()
context.moveTo(50, 50)
context.bezierCurveTo(
300, 100,
100, 300,
300, 300
)
context.stroke()
Courbe de bézier
Courbe de quadratique (de bézier)
context.beginPath()
context.moveTo(50, 50)
context.quadraticCurveTo(
300, 100,
300, 300
)
context.stroke()
Courbe de quadratique (de bézier)
context.globalAlpha = 0.3
context.fillStyle = '#ff0000'
context.fillRect(50, 50, 200, 200)
context.fillStyle = '#00ff00'
context.fillRect(100, 100, 200, 200)
context.fillStyle = '#0000ff'
context.fillRect(150, 150, 200, 200)
La propriété globalCompositeOperation permet de spécifier comment doivent se comporter les tracés en cours (source) par rapport aux tracés initiaux (destination)
Équivalent au pathfinder d'illustrator
context.globalCompositeOperation = 'lighter'
context.fillStyle = '#ff0000'
context.fillRect(50, 50, 200, 200)
context.fillStyle = '#00ff00'
context.fillRect(100, 100, 200, 200)
context.fillStyle = '#0000ff'
context.fillRect(150, 150, 200, 200)
context.fillStyle = 'red'
context.fillRect(200, 200, 200, 200)
context.globalCompositeOperation = 'destination-out' /* source-over | source-in | source-out | source-atop | destination-over | destination-in | destination-out | desination-atop | lighter | copy | xor */
context.beginPath()
context.fillStyle = 'blue'
context.arc(200, 250, 100, 0, Math.PI, false)
context.fill()
Dans les exemples suivants, on considère que le rond rouge est dessiné après le carré bleu
const image = document.createElement('img')
image.addEventListener('load', () =>
{
context.drawImage(image, 0, 0)
const imageData = context.getImageData(0, 0, image.width, image.height)
for(let i = 0; i < imageData.data.length; i += 4)
{
const gray = (imageData.data[i + 0] + imageData.data[i + 1] + imageData.data[i + 2]) / 3
imageData.data[i + 0] = gray
imageData.data[i + 1] = gray
imageData.data[i + 2] = gray
}
context.putImageData(imageData, 0, 0)
})
image.src = 'image.jpg'
Ne fonctionne pas en local !
Pour animer un canvas, on l'efface complètement et on le redessine à chaque frame (jusqu'à 60 fois par seconde)
Il nous faut donc un fonction qui se déclenche à chaque frame : requestAnimationFrame
Request Animation Frame
const loop = () =>
{
window.requestAnimationFrame(loop)
console.log('loop')
}
loop()
La fonction loop sera appelée dès la prochaine frame
Plus l'ordinateur est performant, plus la fréquence sera rapide avec une limite à 60 fps
Exemple : Balle rebondissante
// Coordonnées de base
const ball = { x: 200, y: 200 }
// Fonction déclenchée à chaque frame
const loop = () =>
{
window.requestAnimationFrame(loop)
// Mise à jour des coordonnées
ball.x += 4
ball.y = 200 - Math.abs(Math.sin(Date.now() * 0.005)) * 100
// Limite
if(ball.x > canvas.width + 50)
{
ball.x = -50
}
// Efface le canvas
context.globalAlpha = 0.2
context.fillStyle = '#fff'
context.fillRect(0, 0, $canvas.width, $canvas.height)
// Dessine la balle
context.beginPath()
context.arc(ball.x, ball.y, 50, 0, Math.PI * 2)
context.globalAlpha = 1
context.fillStyle = 'orange'
context.fill()
}
loop()
Exemple : Balle rebondissante
Exemple : Balle qui suit la souris
// Coordonnées de la souris
const mouse = { x: 0, y: 0 }
window.addEventListener('mousemove', (event) =>
{
mouse.x = event.clientX
mouse.y = event.clientY
})
// Coordonnées de la la balle
const ball = { x: 0, y: 0 }
// Fonction déclenchée à chaque frame
const loop = () =>
{
window.requestAnimationFrame(loop)
// Met à jour les coordonnées de la balle en appliquant un easing
ball.x += (mouse.x - ball.x) * 0.1
ball.y += (mouse.y - ball.y) * 0.1
// Efface le canvas
context.globalAlpha = 0.2
context.fillStyle = '#fff'
context.fillRect(0, 0, $canvas.width, $canvas.height)
// Dessine la balle
context.beginPath()
context.arc(ball.x, ball.y, 50, 0, Math.PI * 2)
context.globalAlpha = 1
context.fillStyle = 'orange'
context.fill()
}
loop()
Exemple : Balle qui suit la souris
Aller plus loin
Mes anciens élèves :