Canvas, SVG ja animaatiot
Piirretään canvakselle ja myös SVG-elementeillä. Animoidaan molempia sekä javascriptin että CSS:n avulla.
Canvas
Tutustu seuraaviin dokumentteihin:
- CSS- ja Javascript-animaatiot
- Scalable Vector Graphics (SVG)
- canvas
- Canvas tutorial
- Canvas API
- Drawing on Canvas
- Aiheeseen voi perehtyä myös Drawing In Code-materiaalin avulla
Tee kaikki seuraavat tehtävät suoraan piirtäen canvas-elementille.
- Aloita uusi html-tiedosto. Sijoita tiedostoon canvas-elementti:
<canvas id="canvas" width="800" height="600"></canvas> - Sijoita sivulle canvas-elementin jälkeen myös tavallinen kuva img-elementillä.
Auton kuva latautuu tarkoituksella hitaasti. Tämän takia on tärkeää huomioida joko koko dokumentin load-tapahtuma tai yksittäisen img-elementin load-tapahtuma. Kuvaa ei voida käyttää/käsitellä javascript-sovelluksessa ennen kuin se on latautunut sivulle.<p><img src="https://appro.mit.jyu.fi/cgi-bin/tiea2120/car.cgi" alt="Auto" title="auto" /></p> - Canvakselle voi piirtää sivulle ladattuja kuvia. Kokeile nyt piirtää auto canvakselle. Lisää sivullesi seuraava
javascript-koodi:
// huomioidaan load-tapahtuma eli esim. kaikki kuvat ehtivät latautua ennen koodin suorittamista window.addEventListener("load", function() { let canvas = document.getElementById('canvas'); let img = document.querySelector('img'); let ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); }); - Entäs jos kuva lisättäisiin sivulle dynaamisesti?
Poista html-koodista img-elementti ja lisää auton kuva javascript-koodissa.
Muuta edellinen koodi seuraavanlaiseksi:
kts. Using imageswindow.addEventListener("load", function() { let canvas = document.getElementById('canvas'); let img = document.createElement("img"); img.src = "https://appro.mit.jyu.fi/cgi-bin/tiea2120/car.cgi"; document.body.appendChild(img); // auton kuva voidaan piirtää canvakselle vasta sen latauduttua: img.addEventListener("load", function() { let ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); }); }); - Yritä leikata auton ympäriltä turha tyhjä alue pois. Kokeile drawImage-metodin laajinta versiota, jossa
voit kertoa minkä osan kuvasta haluat piirtää. Viimeiset parametrit kertovat mihin kohtaan kuva piirretään ja missä koossa.
Jos annat saman koon kuin missä kuvan leikkasit niin kuva tulee sellaisenaan canvakselle. Jos annat eri koon niin kuvaa pienennetään
tai suurennetaan. Kokeile saatko kuvasta esim. puolet pienemmän tai kaksi kertaa suuremman.
Kokeile piirtää useampi eri versio autosta. Kaikki versiot tulevat canvakselle päällekkäin.
Kuvan alkuperäisen luontaiseen leveyden ja korkeuden saa naturalWidth- ja naturalHeight-ominaisuuksista. Kannattaa käyttää näitä ennemmin kuin kuvan width- ja height-ominaisuuksia, jotka voivat olla CSS:n tai muun syyn takia skaalattuja.
- Jätä canvakselle vain yksi versio autosta. Piirrä tämän auton alle paksu viiva tieksi. Kts. Drawing shapes. Helpoin tapa piirtää paksu viiva on käyttää fillRect-metodia. Joudut laskemaan oikean paikan riippuen siitä mihin kohtaan piirsit auton ja kuinka korkean auton.
- Kirjoita keskelle canvasta teksti "Hello world!". Kts. Drawing
text. Joudut määräämään käytetyn fontin
ja fontin koon.
Tekstin keskittämiseen löytyy kätevä textAlign-ominaisuus. Kokeile
saada teksti keskelle tämän avulla.
Toinen mahdollisuus tekstin keskittämiseen on tekstin viemän tilan mittaaminen measureText()-metodilla. Keskistä ensin teksti textAlign-avulla ja sen jälkeen tee sama myös measureText-metodin avulla. measureText-metodista voi olla paljon apua viikkotehtävässä.
Kokeile piirtää sama teksti myös suoraan näytön vasempaan yläkulmaan koordinaatteihin 0,0. Näkyykö teksti? Kokeile muuttaa ctx.textBaseline-ominaisuuden arvoksi "top"; Näkyykö teksti nyt?
- Piirrä lomakkeen oikeaan yläkulmaan keltainen aurinko ja sen kaveriksi muutama auringonsäde :) Kts. Drawing shapes: Arcs ja ellipse Säteet voi piirtää viivoilla. Käytetyt värit voi asettaa fillStyle - ja strokeStyle-ominaisuuksilla
-
Lue Transformations.
Piirrä vielä yksi auto mutta käännä tämän auton nokka osoittamaan vasemmalle.
// tallennetaan canvaksen tila ctx.save(); // siirretään origo keskelle autoa. Tämä riippuu siitä mihin kohtaan haluat auton piirtää // ja missä koossa ctx.translate(60, 345); // käännetään x-suunnassa ctx.scale(-1,1); // piirretään varsinainen auto ctx.drawImage(img, 12,30, 120, 90, -60, 0, 120, 90); // palautetaan tila ctx.restore(); - Väännä viimeksi piirtämäsi auto kulkemaan vasemmalle alamäkeen eli 45 asteen kulmaan.
// väännetään vielä kulmaan ctx.rotate((Math.PI / 180) * 45); - Piirrä sinikäyrä
canvaksen taustalle. Skaalaa kuvaaja näyttöön sopivaksi.
ctx.beginPath(); ctx.moveTo(0, 0); let max = 300; let nopeus = 20.0; let laajuus = 200.0; for (let i = 0; i < max; i++) { let y = parseInt( ((Math.sin(1.0 *i / nopeus)+1.0)* laajuus)); ctx.lineTo(i, y); } ctx.stroke(); - Kokeile miten toimivat bezierCurveTo ja quadraticCurveTo
- Piirrä canvakselle kolmen pystypylvään kaavio (bar graph) seuraavasta datasta: demo1: 79, demo2: 81, demo 3: 75. Kirjoita pylväiden otsikot niiden alapuolelle ja pylvään arvo pylvään päälle. Piirrä sopivan muotoiset rectanglet ja tekstit oikeisiin kohtiin.
- Käännä kaaviosi arvoineen ja otsikoineen 90 astetta poikittain.
SVG
SVG-kuvat ovat vektorigrafiikkaa ja canvas käsittelee bittikarttoja. Vektorigrafiikkaa kannattaa käyttää aina kuin vain mahdollista. SVG-kuvia voi tuottaa kuvankäsittelyohjelmilla mutta myös ohjelmallisesti. Keskitytään näissä tehtävissä luomaan ohjelmallisesti yksinkertaisia SVG-grafiikoita. Tarkemmin SVG:hen voit tutustua seuraavien dokumenttien avulla:
- SVG Tutorial
- SVG
- SVG on the web - A Practical Guide
- SVG element reference
- SVG Attribute reference
- Transforms on SVG Elements
Kokeillaan SVG:n käyttämistä:
- Lue Getting Started-sivu. Kokeile sivulla annettua yksinkertaista SVG-kuvaa. Linkitä kyseinen kuva HTML-dokumenttiisi tavalliseen tapaan img-elementin avulla. Kokeile myös miten toimii jos leikkaat ja liimaat koko svg-koodin suoraan HTML-dokumenttiisi. Molempien tapojen pitäisi toimia.
- SVG on siis puhdasta XML:ää. Voit javascriptilla luoda omia SVG-kuvia seuraavaan tapaan. Kokeile HTML-sivullasi seuraavaa
ja vertaa tätä valmiiseen svg-kuvatiedostoon:
// svg-elementit on luotava oikeaan nimiavaruuteen. Tavallinen createElement-metodi // ei kelpaa, koska se luo kaiken HTML:n nimiavaruuteen let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("class", "hbar"); svg.setAttribute("xmlns", "http://www.w3.org/2000/svg"); svg.setAttribute("class", "hbar"); svg.setAttribute("version", "1.1"); svg.setAttribute("width", "300"); // SVG-elementille on syytä määritellä leveys ja korkeus joko suoraan svg.setAttribute("height", "200"); // elementin attribuuteissa tai CSS:n avulla. Ilman kokomääritystä SVG-kuva ei ehkä toimi let rect = document.createElementNS("http://www.w3.org/2000/svg","rect"); rect.setAttribute("x", "0"); rect.setAttribute("y", "0"); rect.setAttribute("width", "100%"); rect.setAttribute("height", "100%"); rect.setAttribute("fill", 'red'); svg.appendChild(rect); let circle = document.createElementNS("http://www.w3.org/2000/svg","circle"); circle.setAttribute("cx", "150"); circle.setAttribute("cy", "100"); circle.setAttribute("r", "80"); circle.setAttribute("fill", "green"); svg.appendChild(circle); let text = document.createElementNS("http://www.w3.org/2000/svg","text"); text.setAttribute("x", "150"); text.setAttribute("y", "125"); text.setAttribute("font-size", "60"); text.setAttribute("text-anchor", "middle"); text.setAttribute("fill", "white"); text.textContent = "SVG"; svg.appendChild(text); document.body.appendChild(svg); - SVG-kuvan ulkoasuun voi vaikuttaa myös suoraan CSS:n avulla silloin, kun kuva on suoraan HTML-dokumentin sisällä tai
luodaan dynaamisesti javascriptilla. Kokeile liittää sivuusi css-tiedosto ja testaile seuraavia tyylimäärityksiä:
text { fill: yellow; font-size: 2em; } circle { fill: blue; }Jos haluat linkittää css-tiedoston suoraan svg-tiedostoon niin se onnistuu lisäämällä rivi:
<?xml-stylesheet type="text/css" href="tyylit.css"?>Kts. SVG and CSS
- Kokeile lisätä joko javascriptilla tai käsin johonkin SVG-kuvaasi viewbox-attribuutti:
Miten tämä vaikuttaa kuvaan? Viewbox määrittää SVG-objektin varsinaisen piirtoalueen. Tämä zoomataan varsinaiseen esityskokoon sen mukaan mitkä ovat svg-elementin width- ja height-attribuutit. Jos width ja height ovat samat kuin viewboxin leveys ja korkeus niin nämä menevät yksi yhteen mutta muissa tapauksissa kuvaa pienennetään tai suurennetaan. Positions.svg.setAttribute("viewBox", "0 0 150 100"); - SVG:stä löytyy suoraan tärkeimmät piirtohahmot. KTs. Basic Shapes. Kokeile piirtää sivullesi vielä jokin polygoni ja ellipsi.
- path toimii vähän samaan tapaan kuin canvakselle piirtäessä. Ensin pitää siirtyä (Move To) kohtaan josta halutaan aloittaa esim. koordinaatteihin 10,10 mentäisiin
merkinnällä M10 10. Seuraavaksi tarvitaan Line To -komentoa esim. L20 20 piirtäisi viivan
alkupisteestä pisteeseen 20,20. Kokeile lisätä seuraava SVG-kuvaasi ja muuttele koordinaatteja:
<path d="M10 10 L20 20" fill="black" stroke="black" /> - Kts. Fills and Strokes
- Kts. Gradients.
Lisää SVG-kuvaasi edellämainitulla sivulla näkyvä defs-lohko ja sinne gradientin luonti. Huomaa, että gradientille on annettava uniikki
id, jotta sitä voidaan käyttää.
<defs> <linearGradient id="Gradient1"> <stop class="stop1" offset="0%"/> <stop class="stop2" offset="50%"/> <stop class="stop3" offset="100%"/> </linearGradient> </defs>Laita edellä luotu gradientti rect-elementin taustalle fill="url(#Gradient1)"
Tarvitset vielä myös css-tyylit:
.stop1 { stop-color: black; } .stop2 { stop-color: yellow; } .stop3 { stop-color: black; }Kokeile miltä kuva näyttää nyt. Testaa erilaisia gradientteja.
- Kts. Texts. Kokeile erilaisia tekstiefektejä.
Miten toimii seuraava:
Polussa oleva C tarkoittaa Bezier Curvea.<path id="my_path" d="M 20,20 C 40,40 80,40 100,20" fill="transparent" /> <text> <textPath xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#my_path">This text follows a curve.</textPath> </text> - Transformoinnit toimivat SVG:ssä samaan tapaan kuin canvaksen yhteydessä. Kts. Basic Transformations
SVG ja tyylit
SVG-kuvien ulkoasun muokkaukseen voi käyttää css-tyylejä samaan tapaan kuin html-dokumenteissa. Tähän on kuitenkin tiettyjä rajoituksia.
Jos SVG-kuva on suoraan svg-koodina html-tiedoston seassa niin html-dokumenttiin linkitetty tyylitiedosto tyyleineen on käytössä.
Erilliseen svg-tiedostoon voidaan myös linkittää ulkoinen css-tiedosto lisäämällä dokumentin alkuun rivi:
<?xml-stylesheet type="text/css" href="style.css"?>
svg-kuvia voi normaalisti linkittää html-dokumenttiin kuten mitä tahansa muitakin kuvia img-elementillä:
<img src="kuva.svg" alt="Logo" />
Mutta tässä tapauksessa svg-tiedostoon linkitettyä ulkoista tyylitiedostoa ei ladata! Jos haluat ulkoisen tyylitiedoston toimivan niin käytä object-elementtiä img-elementin sijaan:
<object type="image/svg+xml" data="testikuva.svg"></object>
Tyylejä voi kirjoittaa myös suoraan svg-tiedostoon (inline) style-elementin avulla tähän tapaan:
<svg width="120" height="240" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="Gradient1">
<stop class="stop1" offset="0%"/>
<stop class="stop2" offset="50%"/>
<stop class="stop3" offset="100%"/>
</linearGradient>
<linearGradient id="Gradient2" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="red"/>
<stop offset="50%" stop-color="black" stop-opacity="0"/>
<stop offset="100%" stop-color="blue"/>
</linearGradient>
<style type="text/css"><![CDATA[
#rect1 { fill: url(#Gradient1); }
.stop1 { stop-color: red; }
.stop2 { stop-color: black; stop-opacity: 0; }
.stop3 { stop-color: blue; }
]]></style>
</defs>
<rect id="rect1" x="10" y="10" rx="15" ry="15" width="100" height="100"/>
<rect x="10" y="120" rx="15" ry="15" width="100" height="100"
fill="url(#Gradient2)"/>
</svg>
Esimerkki: miten img- ja object-elementit eroavat svg-kuvan käyttämisessä
Animointi
Animaatioita voidaan WWW-sivuilla toteuttaa useilla eri tavoilla. Tällä kurssilla käytämme CSS-animaatiota, ja Javascript-animaatiota. SVG-animaatioita SMIL-kielen avulla ei käytetä. Suositus onkin käyttää ennemmin CSS-animointia kuin SMIL-animaatioita.
CSS-animaatiot
Myös CSS-animaatiot ovat helppoja. Käytä CSS-animaatioita ennemmin kuin SMIL-animaatioita. Lue Using CSS animations.
- CSS-animaatiot tehdään kahdessa vaiheessa. Ensin animoitavaan tyyliin määritellään animaation kesto, suunta etc.
- animation-delay
- animation-direction
- animation-duration
- animation-iteration-count
- animation-name määrää animaatiossa käytettävän @keyframes-tyylin nimen
- animation-play-state
- animation-timing-function määrää milla tavalla animointi suoritetaan. Voi käyttää valmiita vaihtoehtoja tai luoda itse omab bezier-käyrän
- animation-fill-mode
h1 { /* animaatio kestää 3s */ animation-duration: 3s; /* käytetään alempana slide-nimistä @keyframes-lohkoa */ animation-name: slide; } @keyframes slide { from { /* alussa elementin on siirretty vasemmalle 100% */ transform: translate(100%,0%); } to { /* animoidaan kunnes on palattu kohtaan 0% */ transform: translate(0%,0%); } } - Animaatioon voi lisätä väliaiheita:
Animaation puolivälissä (50%) fonttikoko on väliaikaisesti 500%. Teksti siis suurenee animaation puoliväliin asti ja sen jälkeen se taas palaa normaaliksi loppuanimaation aikana. Elementtien siirtäminen kannattaa aina tehdä translate-ominaisuudella, joka hyödyntää hyvin näytönohjainten ominaisuuksia. Älä animoi margin-, top-, left-, bottom-, tai right-ominaisuuksia ellei ole aivan pakko.@keyframes slide { from { transform: translate(100%,0%); } 50% { font-size: 500%; } to { transform: translate(0%,0%); } } - Animaation saa jatkumaan ikuisesti kun asettaa animation-iteration-countin arvoksi infinite. Animaation saa myös vaihtamaan
suuntaa. SVG-animaatioissa suunnan vaihtaminen ei onnistu.
animation-iteration-count: infinite; animation-direction: alternate; - Kokeile lisätä edelliseen animaatioon myös tekstin värin animointi
- Laita otsikko liikkumaan näytön kulmasta toiseen. Käytä absoluuttista asemointia
otsikon sijoitteluun
Tee otsikon liikuttelu translate-funktion avulla. Älä animoi left- ja top-ominaisuuksilla. translate osaa paremmin hyödyntää näytönohjainten ominaisuuksia ja on siten tehokas.h1 { position: absolute; /* vasen yläkulma */ left: 0%; top: 0%; }Selainikkunan koko alueella animoitaessa kannattaa vaihtaa yksiköiksi vw ja vh. Prosentit eivät nyt toimi, koska ne suhteutetaan kyseisen elementin kokoon ja absoluuttinen asemointi asettaa elementin leveyden ja korkeuden suoraan sen sisällön mukaan. vw suhteutuu aina selainikkunan leveyteen ja vh selainikkunan korkeuteen.
from { transform: translate(0vw,0vh); } to { transform: translate(100vw,100vh); // animoituu yli näytöstä } - Jos joudut huomioimaan animaatiossa animoitavan objektin kokoa niin
käytä css-tiedostossa calc()-funktiota.
Huomaa, että calc()-funktiolle annetussa laskukaavassa
välilyönnit ovat merkityksellisiä eli välilyöntejä pitää käyttää. Yritä saada otsikko liikkumaan tismalleen
näytön laidasta laitaan:
Selaimen scrollbarit voivat vielä hieman sotkea asiaa. Ne on helpointa poistaa kokonaan näkyvistä ja käytöstä asettamalla body-elementille:to { // vähennetään elementin oma koko koordinaateista transform: translate( calc(100vw - 100%), calc(100vh - 100%) ); }overflow: hidden;Jos liikutat animaatiolla bittikarttaa kts. A couple of Use Cases for Calc(). Myös attr()-funktiosta voi olla apua.
Lisätietoa:
SVG ja SMIL
Tätä ei tarvitse opetella. Käytä CSS-animaatioita.
- SVG-objektien animointi on hyvin yksinkertaista SMILin avulla. Lisää esimerksi circle-elementin sisään animate-elementti:
Kokeile<circle cx="150" cy="100" r="80" fill="green"> <animate attributeName="cx" from="0" to="200" dur="5s" repeatCount="indefinite" /> </circle> - Jos haluat animoida useampia attribuutteja niin vain lisää uusia animate-elementtejä
- transformaatioitakin voi animoida ja silloin käytetään animateTransform-elementtiä:
<polygon points="60,30 90,90 30,90"> <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 60 70" to="360 60 70" dur="10s" repeatCount="indefinite"/> </polygon> - Koska kyseessä ovat taas kerran xml-elementit niin näitä voidaan helposti luoda tai muutella myös javascriptilla
- animateMotion-elementillä voidaan animoida
elementin sijaintia polun avulla. Kokeile seuraavia:
<animateMotion path="M 0 0 H 300 Z" dur="3s" repeatCount="indefinite" /> <animateMotion path="M 250,80 H 50 Q 30,80 30,50 Q 30,20 50,20 H 250 Q 280,20,280,50 Q 280,80,250,80Z" dur="3s" repeatCount="indefinite" rotate="auto" />
Javascript ja animaatiot
Animaatioita voi kontrolloida ja seurata helposti javascriptilla.
- Animaation voi käynnistää, kun asettaa elementille käyttöön tyylin, joka sisältää animaatioita. Yksinkertaisimmillaan
tämä tapahtuu asettamalla elementin class-attribuutille sopiva arvo. Muuta edellä tekemäsi h1-elementin tyylin nimeksi
.otsikko. Aktivoidaan animaatio vasta javascriptilla:
window.addEventListener("load", function() { let h1 = document.querySelector('h1'); // käynnistää animaation h1.className = "otsikko"; }); -
Animaation etenemistä voidaan seurata tapahtumilla:
h1.addEventListener("animationstart", animlistener); h1.addEventListener("animationend", animlistener); h1.addEventListener("animationiteration", animlistener); function animlistener(e) { console.log( e.type + " : " + e.elapsedTime ); } - Javascriptilla voi myös suoraan muokata elementin CSS-ominaisuuksia eivätkä animaatio-ominaisuudet ole poikkeus:
Näiden ominaisuuksien kautta ei kuitenkaan pääse käsiksi paraikaa käytössä oleviin tyylimäärityksiin. Esim. jos tyylissä on määritelty marginaaliksi 10% niin todellisuudessa se on jokin oikea pikselimäärä. Jos haluaa todellisen lasketun tyylin niin se on haettava seuraavasti:h1.style.animationDuration = "5s"; h1.style.animationIterationCount= "3";let style = window.getComputedStyle(h1, null); for (let i of style) { console.log(i+" : "+style.getPropertyValue(i)); } - Myös varsinaisia CSS-animaatioita eli @keyframes-lohkoja voidaan muuttaa tai luoda javascriptilla.
Kts. Using the Web Animations API.
Voit kokeilla miten seuraava vaikuttaa h1-elementille tehtyyn animaatioon:
// h1-elementin animaatiot ja otetaan niistä ensimmäinen let anims = h1.getAnimations(); console.log(anims); let anim = anims[0]; console.log(anim); // haetaan animaation keyframet let keyframes = anim.effect.getKeyframes(); console.log(keyframes); // muutetaan ensimmäisen keyframen transform-arvoa console.log(keyframes[1]); keyframes[1].transform = "translate(50%,50%)"; console.log(keyframes[1]); // asetetaan uudet keyframet anim.effect.setKeyframes(keyframes);Harjoittele Using the Web Animations API -dokumentin avulla.
-
requestAnimationFrame
Javascriptilla voi myös suoraan tuottaa animaatioita eli muutella haluttuja CSS- tai muita ominaisuuksia HTML- tai SVG-elementeistä. Yleensä kannattaa käyttää CSS-animaatioita jos suinkin mahdollista, koska selaimet on optimoitu näiden suhteen ja osaavat hyödyntää mm. näytönohjaimia. Jos teet puhdasta Javascript-animointia niin siinä on käytettävä requestAnimationFrame-metodia// kutsutaan requestAnimationFrame ja järjestelmä pitää huolen, että // jossain vaiheessa kutsutaan parametrina tuotua js_animaatio-funktiota // jos ikkuna tai tab on taustalla niin tätä ei välttämättä tapahdu window.requestAnimationFrame(js_animaatio); let paikka = 0; function js_animaatio() { // tämä kannattaisi hakea valmiiksi muistiin globaaliin muuttujaan let anim = document.getElementById("animsvg"); // animoidaan koko objekti näytön yli muuttamalla left-ominaisuutta (position: absolute; täytyy olla käytössä) anim.style.left = paikka + "px"; paikka++; if ( paikka > document.body.clientWidth ) paikka = 0; // täytyy itse kutsua uudelleen jos halutaan jatkaa samaa window.requestAnimationFrame(js_animaatio); } - Toinen vaihtoehto on käyttää Javascriptin timereita kuten setInterval() mutta tämä on tehotonta verrattuna requestAnimationFrameen eikä pidä käyttää animaatioissa.
Lisätietoa:
Käyttäjien kommentit