Gå til indhold

HTML5+JS

Effekter oprettes med ren JavaScript på et HTML5-canvas-element. I dette afsnit gennemgår vi det grundlæggende: tegning af figurer, tegning med iteration og tilføjelse af bevægelse.

I denne vejledning bruger jeg en generisk lightscript-skabelon:

<head>
<meta description="Template"/>
<meta publisher="WhirlwindFX" />
</head>
<body style="margin: 0; padding: 0;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
// Get the canvas element from the DOM
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
var width = 320;
var height = 200;
var hue = 0;
function update() {
// Code goes here
window.requestAnimationFrame(update);
}
window.requestAnimationFrame(update);
</script>

Uanset om du bruger én linje eller mange, er processen den samme.

  1. ctx.beginPath() starter din figur
  2. ctx.moveTo(x, y) sætter det første punkt
  3. ctx.lineTo(x, y) sætter det næste punkt og en linje tilbage til det første. Brug flere lineTo’er til at tegne større figurer.
  4. Angiv ctx.strokeStyle og/eller ctx.fillStyle (hvis figuren er lukket)
  5. Brug ctx.stroke() til at tegne linjerne og ctx.fill() til at udfylde lukkede figurer. Det er vigtigt at bemærke, at alt indtil dette punkt er hypotetisk – intet tegnes, før disse kommandoer udføres.

Her er tre eksempler med linjer:

function update() {
// One Line
ctx.beginPath();
ctx.moveTo(30, 30);
ctx.lineTo(30, 170);
ctx.strokeStyle = "blue";
ctx.stroke();
// L
ctx.beginPath();
ctx.moveTo(80, 30);
ctx.lineTo(80, 100);
ctx.lineTo(120, 100);
ctx.strokeStyle = "blue";
ctx.stroke();
// Filled Triangle
ctx.beginPath();
ctx.moveTo(130, 30);
ctx.lineTo(130, 100);
ctx.lineTo(170, 100);
ctx.lineTo(130, 30);
ctx.fillStyle = "red";
ctx.strokeStyle = "blue";
ctx.fill();
ctx.stroke();
window.requestAnimationFrame(update);
}

Læg mærke til tegnerækkefølgen her: den udfyldte trekant tegnes sidst, så den ville dække de to andre figurer, hvis de var på samme position. Desuden tegnes trekantens streg efter udfyldningen. Hvis vi byttede om på disse trin, ville den indre halvdel af stregen blive dækket af den røde udfyldning.

Rektangler er enkle og nyttige, især til den gennemsnitlige opløsning på et RGB-tastatur. Processen ligner tegning af linjer – erstat blot kommandoerne moveTo og lineTo med rect(x, y, width, height).

function update() {
// Basic Square
ctx.beginPath();
ctx.rect(30, 30, 75, 75);
ctx.strokeStyle = "blue";
ctx.stroke();
// Filled Rectangle
ctx.beginPath();
ctx.rect(125, 30, 125, 100);
ctx.fillStyle = "red";
ctx.strokeStyle = "blue";
ctx.fill();
ctx.stroke();
window.requestAnimationFrame(update);
}

Buer tegnes ved at rotere et ydre punkt omkring et centralt punkt og kan bruges til at lave hele cirkler. Kommandoen for buer er ctx.arc(x, y, radius, start angle, end angle), hvor begge vinkler angives i radianer. I modsætning til rektangler tegnes en bue med figurens origo (x, y) i midten af buen.

function update() {
// Half-circle
ctx.beginPath();
ctx.arc(70, 100, 30, 0, Math.PI);
ctx.strokeStyle = "blue";
ctx.stroke();
// 3/4 circle
ctx.beginPath();
ctx.arc(140, 100, 30, 0, Math.PI * (3/2));
ctx.strokeStyle = "blue";
ctx.stroke();
// Full circle
ctx.beginPath();
ctx.arc(210, 100, 30, 0, Math.PI * 2);
ctx.strokeStyle = "blue";
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
window.requestAnimationFrame(update);
}

Som du kan se, tegnes buer som standard med uret. Det kan vendes om med en slutvinkel, der er mindre end startvinklen, eller ved at tilføje “true” som sidste argument til metoden ctx.arc().

At tegne tre figurer i hånden kan nemt kræve tyve linjer kode, så hvis du skal tegne hundrede figurer, har vi brug for en mere effektiv tilgang. Løkker i JavaScript er en enkel måde at gentage opgaver på, og de kan også bruges til tegning.

Her er et eksempel, der bruger en for-løkke til at tegne en række ternede firkanter:

function update() {
for(let i = 0; i < 8; i++){
// 1. Create rectangle path and draw the stroke for each.
ctx.beginPath();
ctx.rect(i * 20, 20, 20, 20);
ctx.strokeStyle = "black";
ctx.stroke();
// 2. If 'i' is even, the fillStyle is black. Otherwise it's white.
if(i % 2 == 0){
ctx.fillStyle = "black";
} else {
ctx.fillStyle = "white";
}
// 3. Fill the shape after setting the fillStyle.
ctx.fill();
}
window.requestAnimationFrame(update);
}

Her er et eksempel med en while-løkke, der tegner et ternet gitter. Jeg ændrer lysheden af de sorte firkanter og farvetonen af de hvide firkanter en smule i hver iteration ved hjælp af template literals.

function update() {
var i = 0;
while(i < 16){
// 1. X and Y calculations with 'row' and 'column'
// This operation rounds the result down to the nearest integer. For i = (0-3) the row will be 0, for i = (4-7) the row will be 1, and so on
var iRow = Math.floor(i / 4);
// This operation finds the remainder after division, limiting column to the (0-3) range.
var iCol = i % 4;
// Multiply by the square width to find this square's x-origin
var ix = iCol * 20;
// Multiply by the square height to find this square's y-origin
var iy = iRow * 20;
// 2. Path and stroke
ctx.beginPath();
ctx.rect(ix, iy, 20, 20);
ctx.strokeStyle = "black";
ctx.stroke();
// 3. If the row is even, every other square is filled black. If odd, switch the black squares. Each iteration adds a small amount of the lightness component to the black, and hue to the white.
if(iRow % 2 == 0){
if(i % 2 == 0){
// 'Black'
ctx.fillStyle = `hsl(1, 0%, ${5 * i}%)`
} else {
// 'White'
ctx.fillStyle = `hsl(${10 * i}, 100%, 50%)`;
}
} else {
if(i % 2 == 0){
// 'White'
ctx.fillStyle = `hsl(${10 * i}, 100%, 50%)`;
} else {
// 'Black'
ctx.fillStyle = `hsl(1, 0%, ${5 * i}%)`
}
}
ctx.fill();
i++;
}
window.requestAnimationFrame(update);
}

Animationer på canvas’et er kun begrænset af din fantasi og din matematiske viden. Hvis du føler dig usikker på trigonometri eller geometri, anbefaler jeg at læse lidt op på det for at løfte dine færdigheder til næste niveau. I de næste eksempler tager jeg en stillestående cirkel og knytter alle mulige variabler til en simpel svingende bevægelse.

Den gyldne standard for oscillation (regelmæssig frem-og-tilbage-bevægelse) er at tage sin() eller cosine() af en timer. Begge operationer returnerer en værdi mellem -1 og 1 for ENHVER indsat værdi, selv hvis den blot tæller opad. Den eneste forskel på de to er, at de er lidt ude af takt – når cos er 0, er sin -1 eller 1, og omvendt. Det kan vi udnytte i denne animation –

function update() {
// Find current time in milliseconds, then divide to slow down the animation speed
let time = Date.now() / 100;
// Draw a background so the old shapes don't stick around
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
ctx.beginPath();
//USE ONE EXAMPLE AT A TIME
// 1st example - side to side, y is constant
ctx.arc(15 * Math.cos(time) + 160, 100, 30, 0, Math.PI * 2);
// 2nd example - up and down, x is constant
ctx.arc(160, 15 * Math.sin(time) + 100, 30, 0, Math.PI * 2);
// 3rd example - circle, x and y are variable
ctx.arc(15 * Math.cos(time) + 160, 15 * Math.sin(time) + 100, 30, 0, Math.PI * 2);
ctx.fillStyle = "black";
ctx.fill();
window.requestAnimationFrame(update);
}

Vi har kun nogle få egenskaber mere at ændre over tid – cirklens radius, udfyldning og streg. Jeg lader buevinklerne være i fred for nu for at holde figuren enkel, for resten bliver ret prangende.

function update() {
let time = Date.now() / 100;
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
ctx.beginPath();
ctx.arc(15 * Math.cos(time) + 160, 15 * Math.sin(time) + 100, 30 + 15 * Math.sin(time), 0, Math.PI * 2);
ctx.fillStyle = `hsl(${180 + 180 * Math.cos(time)}, 100%, 50%)`;
ctx.fill();
ctx.strokeStyle = `hsl(${180 + 180 * Math.sin(time)}, 100%, 50%)`;
ctx.stroke();
window.requestAnimationFrame(update);
}

Alle egenskaber ved alle de figurer, vi har lavet indtil nu, kan ændres over tid, og hver animation, jeg laver, er en kombination af ovenstående færdigheder. For flere detaljer om at skabe genanvendelige, effektive animationer kan du se vores side om Callbacks!