oCanvas: A jQuery

oCanvas: En jQuery- og Flash-Style Bibliotek for HTML5 Canvas
en
Del
19
Del
Cyber ​​Monday Envato Tuts + kurs vil bli redusert til bare $ 3. Ikke gå glipp av.

Med bruk av verktøy som Adobe Edge og biblioteker som EaselJS, flere ressurser blir tilgjengelig for utviklere som ønsker å lage interaktive HTML5 innhold. Mange av disse verktøyene blir rettet spesielt for Flash-utviklere å gjøre overgangen fra Action til HTML5 lerret en glatt. Denne artikkelen vil oversikten oCanvas, en HTML5 bibliotek som utviklere kan ikke bare finne uvurderlig, men også svært enkelt å bruke.




HTML5 Canvas Bakgrunn

Før vi dykke til å utforske oCanvas, la oss raskt duket for hvordan HTML5 lerret fungerer. Hvis du ønsker en mer grundig forklaring på hvordan du bruker HTML5 lerret, sjekk ut denne opplæringen.

Hvis du vet Action, du allerede vet mye av Javascript, som er der den reelle makten ligger når du arbeider med lerret. Vi bruker HTML5 tegningen API for å lage vårt innhold sammen med noen gode gamle Javascript for å få ting til interaktiv og dynamisk. Men når vi kombinerer de to, er tilnærmingen bak hvordan vi går om å sette sammen vår kode litt annerledes enn hva vi er vant til med Actionscript.

Kort sagt, for å bruke mors Canvas API, trekker vi piksler på tegningen sammenheng med lerretet. Men det viktigste å huske på er at vi jobber med hele lerretet, ikke bare en enkelt form eller bilde som vi har trukket. Hver gang vi ønsker å forandre noe vi har trukket må vi tegne hele lerretet. Hvis vi ønsker å animere noe må vi tegne lerretet igjen og igjen i vår Javascript for å gjøre det virke som om ting er i bevegelse.

Denne oppfatningen er veldig lik tradisjonell animasjon, der animatører måtte trekke hver posere i sin sekvens og ha kameraet beveger seg gjennom dem svært raskt å simulere bevegelsen. Men hvis du er vant til trelignende strukturer som DOM, eller liste displayet i Actionscript, kan dette begrepet være vanskelig å få hodet rundt. Dette skyll og gjenta tilnærming til programmering er mye annerledes enn å jobbe med objekter for de fleste utviklere.



presenterer oCanvas

Heldigvis for de av oss som er så vant til å arbeide med objekter, bringer oCanvas som kjent tilnærming til HTML5 lerret. oCanvas er en Javascript-bibliotek som er utviklet av Johannes Koggdal med den hensikt å gjøre det enklere å utvikle med HTML5 lerret. Det gjør at du kan arbeide direkte med objekter, endre deres egenskaper og hekte hendelser til dem alle ved håndtering kjedelige bak kulissene ting for deg. Som satt best ved Johannes på sin blogg:

Mitt mål har alltid vært å gjøre det veldig enkelt for folk å bygge lerret ting basert på objekter. Jeg bestemte meg på navne oCanvas som en sammentrekning av "objekt lerret".



Få Bibliotek

Hvis du vil begynne å bruke oCanvas vi trenger for å ta en kopi av biblioteket på vår HTML-side . Vi kan enten referere til CDN-vert fil, eller arrangere en lokal kopi selv. Hoppe over til oCanvas nettstedet, og du kan enten laste ned en kopi av biblioteket eller hente referansen til CDN-vert versjon. Den gjeldende versjonen er 2.0 og ble utgitt bare noen få uker siden, som adressert mange av de feilene som var i den første utgivelsen. På stedet er det en minified produksjonsversjon, noe som er bra å bruke når du er klar til å distribuere prosjektet. Det er også en utvikling versjon, som er ukomprimert, men er bedre for debugging. Liker jeg å lenke direkte til den vert versjon for raskere lasting og mellomlagring av nettleseren
. ≪ script src="http://cdnjs.cloudflare.com/ajax/libs/ocanvas/2.0.0/ocanvas.min.js"></script>



Initial Kode Setup

Når du har en referanse til oCanvas, neste vi trenger å sette opp et lerret element i kroppen vår HTML og opprette en referanse til den for bruk i vår Javascript
. ≪ lerret id = "lerret" width = "800" height = "600" > < /lerret >

Som alltid, hvis du plasserer skriptet ovenfor lerretet element, må du pakke det inn i en funksjon slik at du vet at DOM er ferdig. Det er et par måter å gå her. Du kan enten lage din egen funksjon og deretter ringe det i kroppen din element når det lastes, slik:
funksjonen main () {//din oCanvas kode} < body onload = "main ();" > < p> Eller du kan pakke inn koden din innen oCanvas innebygde domReady () -metoden. Dette er ekvivalent med jQuery er $ (document) .ready (). I oCanvas bruker vi dette:
oCanvas.domReady (function () {//din kode her});

. Merk: Du kan bruke jQuery er $ (document) .ready () metoden hvis du ønsket å



Initial en forekomst av oCanvas

Dette stykke kode er absolutt nødvendig, og er det første du må skrive når du bruker oCanvas
Var lerret = oCanvas.create ({lerret.: "#canvas", bakgrunns: "# 0cc", fps: 60});

I denne koden lagrer vi en referanse til lerretet element i vårt dokument og få tilgang til kjernen eksempel, som vil gjøre deg i stand til å begynne å lage objekter. Den skaper () metoden tar et objekt som sitt argument som styrer hvordan oCanvas vil fungere. Det er mange egenskaper til å passere inn i lag () metoden, men den eneste obligatorisk ene er lerretet eiendommen: a. CSS velgeren som må peke på et lerret element innenfor DOM

De andre egenskapene gått i oven kode er bakgrunnen og fps egenskaper. Bakgrunnen egenskapen kan du bruke en bakgrunn til lerretet, noe som kan være CSS fargeverdier, graderinger og bilder. Hvis det er utelatt, vil lerretet være gjennomsiktig. Den fps eiendom komplekser antall bilder per sekund noen animasjon vil kjøre på. Standard er 30 fps

Merk:.. Mens vi oversikten mange av funksjonene i oCanvas, anbefaler jeg å sjekke ut biblioteket dokumentasjon for å få en bedre forståelse av hver seksjon



Display Objekter

Det finnes mange typer visningsobjekter du kan lage med oCanvas. Du kan lage former som rektangler, ellipser, polygoner og linjer sammen med bilder, tekst og selv sprite ark. For å opprette en ny skjerm objekt vi bruker oCanvas visnings Module, og angi hva slags skjerm objektet vi ønsker å skape, samt noen grunnleggende egenskaper - som så:
Var boks = canvas.display.rectangle ({x: 50, y: 150, bredde: 50, høyde: 50, fyll: "# 000"});

Så for å legge den til skjermen vi kaller en kjent metode for deg Flash-utviklere ...

God Ol 'addChild ()

Ja en oldie men en goodie, som gjør legge til objekter i oCanvas en kjent prosess. Så for å legge vår boksen til lerretet, ville vi skrive:
canvas.addChild (boks);

Akkurat som i Actionscript, addChild () legger den angitte objektet som et barn av den som ringer. Og i sin tur barnets x og y vil være i forhold til den overordnede. Så i dette tilfellet er vi gjør boksen et barn av lerretet, som vi kunne forenkle slik:
box.add ();

add () metoden legger også gjenstand til lerretet - som er virkelig samme som canvas.addChild (boks). Men addChild () er mest nyttig for å legge et objekt som et barn i en allerede opprettet skjerm objekt, som:
Var square = canvas.display.rectangle ({x: 0, y: 0, bredde: 10, høyde: 10, fyll: "# 990000"}); box.addChild (firkant);

La oss ta en titt på noen av de ulike typer visningsobjekter du kan lage i oCanvas.

Shapes

Du har allerede sett en firkant, men vi kan bruke rektangelet skjermen objektet for å skape en masse ting. Her er et rektangel med en blå strek:
Var rektangel = canvas.display.rectangle ({x: 500, y: 100, bredde: 100, høyde: 200, fyll: "# 000", hjerneslag: "utenfor 2px blå "});

Fyll eiendommen kan ta noen gyldig CSS farge, sammen med CSS gradienter og selv bildemønstre

Hvis du vil opprette en ellipse vi ville skrive:
Var ellipse = canvas.display.. ellipse ({x: 100, y: 100, radius_x: 20, radius_y: 30, fyll: "RGBA (255, 0, 0, 0.5)"});

Hvis du vil ha en full sirkel, bare erstatte radius_x . og radius_y eiendommer med et enkelt radius eiendom

Opprette noen form for vanlig polygon er like enkelt - alt du trenger å gjøre er å oppgi antall sider og radiusen du vil at formen skal ha. For å opprette en trekant:
Var trekant = canvas.display.polygon ({x: 320, y: 145, sider: 3, radius: 50, fyll: "# 406618"});

Hva med en femkant ?
Var pentagon = canvas.display.polygon ({x: 200, y: 50, sider: 5, rotasjon: 270, radius: 40, fyll: "# 790000"});

For å oppnå dette med HTML5 lerret API, ville du må tegne en haug med stier og prøve å finne ut hva x og y stillinger for å bli med dem på. Jeg forsøkte å tegne en åttekant for sammenligning skyld, men som du kan se nedenfor jeg ga opp ganske enkelt. Ikke helt sikker på hva dette er ment å være.
Var lerret = $ ("# lerret"); Var ctx = canvas.get (0) .getContext ("2d"); ctx.fillStyle = '# 000'; ctx.beginPath (); ctx.moveTo ( 0, 0); ctx.lineTo (100,50), ctx.lineTo (50, 100); ctx.lineTo (0, 90); ctx.closePath (); ctx.fill ();
Bilder

Opprette visningsobjekter med bilder blir ikke noe enklere enn i oCanvas. Bare angi en x og y posisjon og banen til bildefilen:
Var treet = canvas.display.image ({x: 100, y: 350, bilde: "tree.png"});

En fin funksjon av bildevisningen objektet er flisen eiendom, som lar deg enkelt lage et rutenett av det samme bildet i stedet for å tegne den om og om igjen.

Tekst

oCanvas inneholder en tekst display objekt og håndterer font styling akkurat som CSS gjør
Var text = canvas.display.text ({x:. 70, y: 300, align: "sentrum", font: "dristige 18px sans-serif", tekst: "oCanvas Rocks ", fylle:" lilla "});

Du kan bruke mange av de andre tekstegenskaper du er kjent med fra CSS. Sjekk ut dokumentasjon på tekst for mer.



Egenskaper og metoder

Alle skjermobjekter arve en felles gruppe av egenskaper og metoder. Noen av de vanligste skjermobjektegenskapene er: x, y, bredde, høyde, rotasjon, scalingX, scalingY, opasitet, (bruker CSS-box-shadow syntaks) skygge og zIndex. Du kan sjekke ut denne linken for en fullstendig liste over de grunn egenskaper og metoder. La oss ta en titt på noen andre viktige funn.

Origin

Denne metoden er en stor tidsbesparende fordi det lar deg enkelt sette start inne i objektet. Med andre ord, det kan du stille inn registreringspunktet på objektet. Hvis du noen gang prøvd å utføre en dreining fra sentrum med HTML5 Canvas API, vet du hvor stor av en hodepine det kan være. Du må gjøre massevis av handlinger for å redde tegningen staten, sette lerretet, utføre din rotasjon og deretter gjenopprette tegningen staten. Med opprinnelse eiendommen kan du enkelt definere et objekt opprinnelse:
Var obj = canvas.display.image ({x: 270, y: 270, opprinnelse: {x: "sentrum", y: "center"}});

Dette ville tegne bildet fra midten; hvis vi skulle rotere objektet, ville det dreie fra sitt sentrum, også. Foruten "sentrum" du kan også passere i "venstre" eller "høyre" for x og "topp" eller "bottom" for y stillinger. I tillegg til å bruke de forhåndsdefinerte søkeord, kan du også levere positive eller negative tall som verdier for hvor de skal trekke objektet. Standard opprinnelse for alle visningsobjekter er definert ved sin venstre

Du kan også bruke den setOrigin () -metoden når som helst å definere et objekt opprinnelse.
Obj.setOrigin ("venstre", " bottom ")
ID

En skjerm objektets id, som er virkelig en skrivebeskyttet eiendommer tilsvarer hvor objektet finnes i trekningen liste - som du kan tenke på som listevisning. Jeg synes det å være svært nyttig fordi det kan tjene som en unik identifikator i visse situasjoner når du kan oppsøke et bestemt objekt i koden. Vurdere en enkel tekstutdrag som dette: "galt"
getId (box.id) -funksjonen getId (id) {if (id == 9) console.log ("! RIKTIG" + id) annet console.log (+ id)}
Composition

Sammensetningen eiendom tilsvarer globalCompositeOperation innenfor de innfødte Canvas API. Hvis du ikke er kjent med det, i utgangspunktet avgjør det hvordan piksler gjengis når trekkes inn allerede eksisterende piksler på lerretet. Jeg oppfordrer deg til å lese opp på de ulike komposisjons operasjonene du kan stille, men med oCanvas du kan bare sette den operasjonen du ønsker ved å sende det som en streng:
Var form = canvas.display.rectangle ({x: 270, y: 270, bredde: 180, høyde: 80, fyll: "# ff6900", komposisjon: "mål-toppen"});

Det er mange forskjellige operasjoner du kan passere i, men jeg tror en av de pene tingene du kan gjøre med sammensetningen eiendommen er å lage masker mellom ulike visningsobjekter. Sjekk ut filen som heter masks.html i nedlastingspakken. Hvis du noen gang avhengig av å skape lagmasker i dine Flash-applikasjoner, vil du like denne.

Metoder for Note

Siden vi nevnte roterende gjenstander tidligere, kan du raskt rotere et objekt med rotere () og rotateTo () metoder:
obj.rotate (45);

Du kan også bare angi rotasjons eiendommen:
obj.rotation = 45;

Det er også farten () og moveTo () metoder som i likhet med navnene antyder, kan du flytte et objekt av en spesifisert mengde piksler for det tidligere og toa spesifisert x og y posisjon for sistnevnte.
obj.moveTo (100, 100)

Den samme ideen fungerer for skalaen () og scaleTo () metoder ():
obj.scale (1,25, 0,25) obj.scaleTo (1.5, 1.5)

Vi nevnte addChild () før; la oss ikke glemme removeChild () og removeChildAt (). Og som add () metoden, kan vi gjøre det motsatte med remove ().

En annen veldig nyttig metode er klone (), som gjør det mulig å kopiere et visningsobjekt og alle dens egenskaper. Anmeldelser Var boks = canvas.display.rectangle ({x: 50, y: 150, bredde: 50, høyde: 50, fyll: "# 000"}); Var BOX2 = box.clone (x: 200)


< h2> Arrangementer

Et stort pluss til oCanvas er at du kan legge til hendelser til bestemte objekter. Den oCanvas inneholder mange metoder og egenskaper for lett håndtering mus, tastatur og selv ta arrangementer hele med en enkel metode.

Bind ()

Hvis du er kjent med jQuery, har du sannsynligvis allerede vet hvor jeg kommer med dette
canvas.bind ("klikk tap", function () {canvas.background.set ("# efefef");});.

Alt dette gjør er å endre bakgrunnsfargen av lerretet, men legg merke til hvordan vi passere i "klikk tap" - lett tillater oss å legge til støtte for både mus og touch-enheter

I tillegg til klikk hendelser, kan du også lytte til andre mus hendelser:. mousedown, mouseup , mousemove, mouseenter, mouseleave og DblClick.

En enkel rollover effekt kan se slik ut:.
box.bind ("mouseenter", function () {canvas.background.set ("# 333");}) bind ("mouseleave" , function () {canvas.background.set ("# 000");});.

Dette er et eksempel på kjeding funksjoner - som (for ikke å høres ut som en knust rekord) er en annen jQuery funksjonen utnyttes i oCanvas

Men i stedet for å endre lerretet når en mus hendelse inntreffer, hva med å endre en selve skjermen objekt? Dette er fortsatt HTML5 Canvas tross alt, så vi må huske å ringe en viktig metode for å fortelle lerretet for å oppdatere seg selv

canvas.redraw ()

tegning () -metoden. (Som er faktisk en del av Draw Module, ikke Hendelser Module) tegner lerretet med alle visningsobjekter som har blitt lagt. Så hvis du ønsker å utføre en handling på et bestemt objekt og har resten av listen uavgjort forbli intakt, må vi legge dette en enkel linje med kode til våre funksjoner:
square.bind ("klikk tap", funksjon () {square.x + = 50; canvas.redraw ();});
Frigi ()

Hva er vitsen med en hendelse lytteren hvis vi ikke kan fjerne det
rectangle.bind ("? Klikk på trykk ", funksjon onClick () {this.fill =" # FF9933 "; canvas.redraw (); rectangle.unbind (" klikk tap ", onClick)});?
Hvordan Om en Quick Dra og slipp

Vi trenger ikke bind () metode for dette. Vi bare skrive.
Circle.dragAndDrop ();

Det er sannsynligvis den raskeste og enkleste dra og slipp-koden du noensinne vil skrive

Merk av hendelser: Når du arbeider med hendelser, det er naturlig å ønske å få så mye informasjon som mulig om hendelsen. Heldigvis kan vi fortsatt gjøre det når du arbeider med oCanvas. For eksempel hvis vi tar et klikk behandleren noen linjer opp og logge arrangementet til konsollen vi kan se alle de egenskapene vi har fra arrangementet.
Rectangle.bind ("klikk tap", funksjon onClick (e) {dette .fill = "# FF9933"; canvas.redraw (); rectangle.unbind ("klikk tap", onClick); console.log (e);});
Tastatur og Touch Hendelser

Foruten mus hendelser, har oCanvas hele moduler dedikert til tastatur og berørings hendelser med sine egne unike metoder og egenskaper. Disse hendelsene er også behandlet med bind () -metoden. Hendelsene system i oCanvas er et veldig bredt tema, så jeg oppfordrer å ta en titt på hendelser delen i dokumentasjon og eksperimentere.



Tidslinje

Med Tidslinje Module vi kan stille opp vår viktigste løkke for søknaden vår. Hvis du skulle lage et spill, vil dette i hovedsak være spill loop. Jeg liker å tenke på det som tilsvarer en ENTER_FRAME i Flash

Det er enkelt å sette opp - vi bare ringe setLoop funksjon og kjede start () metoden til det.
Canvas.setLoop ( function () {triangle.rotation + = 5;}) start ();

Hvis vi ønsket å knytte setLoop () -funksjonen til en hendelse - si til et museklikk - vi kunne gjøre noe som dette.
canvas.setLoop (function () {triangle.rotation + = 5;}) button.bind ("klikk tap", function () {canvas.timeline.start ()});

Og vi kunne stoppe tidslinjen ved ganske enkelt ringer:
canvas.timeline.stop ();



Animasjon

Ved hjelp setLoop () er veien å gå for animasjoner som vil skje over en lang periode, og til å håndtere konstant oppdaterer du har å gjøre gjennom hele programmet. Men oCanvas har bygget i metoder for å håndtere mer enklere og grunnleggende animasjoner som ofte trengs. Disse metodene er også praktisk talt tatt ordrett fra jQuery.

Animate ()

animere () metoden fungerer akkurat som det gjør i jQuery. Hvis du ikke er kjent med denne siden av jQuery, tror at hvis det liker en tweening motor som TweenMax eller Tweener for Flash. Du kan animere noen eiendom som kan settes av en tallverdi:
circle.animate ({y: circle.y - 300, scalingX: 0,5, scalingY: 0,5}, "short", "lette-in" , function () {circle.fill = "# 45931e"; canvas.redraw ();});

Her er vi animere sirkelens y posisjon og totale størrelse, bruke noen lettelser, og når den er ferdig kjører vi en tilbakeringing funksjon som endrer sin fyllfarge. Men ikke glem å ringe tegne ()

fadeIn (), fadeout (), og fadeTo ()

For å falme et objekt inn og ut vi kunne bare ringe.
square.fadeIn (); square.fadeOut ();

For å visne opacity til en bestemt verdi, vil vi bruke fadeTo ():
square.fadeTo (0,6);

Du kan også definere varigheten, lettelser og gi en tilbakeringingsfunksjonen for disse metodene på samme måte som du ville gjort med animere () metoden.



Scener

oCanvas inneholder et svært nyttig Scener Modul som gjør at du enkelt kan skille din søknad i forskjellige stater. Spillutviklere kan sette pris på dette fordi det er en enkel måte å bryte ned spillet inn i ulike seksjoner. Selv gamle skolen Flash animatører kan sammenligne den Scener Module til Scenes panel, som lar deg bokstavelig talt lage ulike scener i en Flash-prosjekt.

For å opprette en scene i oCanvas vi kaller lage () metode for å returnere en scener innvende:
Var intro = canvas.scenes.create ("intro", function () {//legge visningsobjekter her});

Innenfor skape () metoden vi passerer i to argumenter: navnet scenen som en streng, og en funksjon der vi legger displayet objektet vi ønsker å legge til at scenen
Var INTROTEXT = canvas.display.text ({x:. canvas.width /2, y: canvas.height /2, justere: "center", font: "dristige 36px sans-serif", tekst: "Introduction", fyll: "# 133035"}); Var intro = canvas.scenes.create ("intro", function () { this.add (introtekst);});

Nå må vi laste vår scene, og disse objektene vil bli lagt til displayet:
canvas.scenes.load ("intro");

Legg merke til vi passerer i navnet vi ga scenen når vi laget den

Og selvfølgelig vi kan lesse en scene når som helst:
canvas.scenes.unload ("intro");

Tenk hvor mye. av en tidsbesparende dette kan være hvis du brukte scener og event handlers sammen.



oCanvas vs. EaselJS

Den eneste reelle ulemper til oCanvas er at det ikke har fått som mye trekkraft i utviklingen samfunnet som du kanskje skjønner - eller i det minste synes det på den måten for nå. En del av den grunn for dette, tror jeg, er på grunn av populariteten til EaselJS. Det synes å være mye mer oppmerksomhet og ressurser der ute for EaselJS enn det er for oCanvas - som er vanskelig å tro siden sistnevnte først ble lansert i mars 2011, men for noen grunn har det fløyet under radaren
Jeg har brukt både biblioteker for en stund nå, og jeg kan ærlig si at jeg er stor fan av begge. EaselJS definitivt føles mer som du bruker Actionscript, og hvis du er en Flash-utvikler vil være lett å plukke opp. Og som vi har sett, kan oCanvas passere for jQuery lange mistet broren på mange måter. Så hvis du er en ren ActionScripter, du kan bare naturlig bevege mot EaselJS- spesielt siden staffeli ble skrevet spesielt for å appellere til Flash-utviklere.

Men jeg har brukt Action mye lengre enn jQuery og jeg personlig finne oCanvas enklere å bruke og mindre ordrik å skrive. Og selv om EaselJS er ganske enkel å bruke selv, enkel syntaks i oCanvas bare gjør det en velkommen verktøy.

Men i tillegg til enklere syntaks, oCanvas og EaselJS på mange måter er er ganske mye om hverandre. Begge bibliotekene kan oppnå mer eller mindre de samme oppgavene, og det er svært liten forskjell i ytelse, hvis noen. Men jeg legger merke til at Ticker-funksjonen i EaselJS går litt mer jevnt enn oCanvas 'setLoop funksjon (selv om det bare kunne være et nettbasert forskjell)

EaselJS har mye mer av en omfattende API -. Særlig når det kommer til tegning og effekter. Og hvis du tar hensyn TweenJS og SoundJS, er staffeli definitivt et mer komplett verktøy - spesielt hvis du er vant til å bruke et program som Flash som tilbyr finjustere kontroll over dine prosjekter. Men hvis du er ny på hele HTML5 spillet, vil du sannsynligvis til å treffe bakken kjører med oCanvas mye raskere. Da jeg først ble introdusert til oCanvas, fant jeg det så gøy å leke med. Alt er allerede der for deg - alle nødvendige metoder og hendelser for å begynne å lage, manipulere og animere objekter med en gang



innpakning opp

Uansett hvilket bibliotek du foretrekker, oCanvas og EaselJS er. bare begynnelsen på hva jeg tror vil være en strøm av verktøy og ressurser for å tillate utviklere å enkelt lage nettleserbaserte applikasjoner. Funksjonene i oCanvas beskrevet i denne artikkelen knapt riper i overflaten av hva som kan bli laget med denne svært kraftig bibliotek.

På ingen måte er skjønt oCanvas (eller biblioteket for den saks skyld) en grunn til ikke å lære og bruke mors HTML5 Canvas API. Men hvis du befinner deg i en situasjon der alle dine tidligere Flash kunder er nå på jakt etter deg å lage HTML5 apps (som min var), og du ikke har tid til å lære noe som uvennlig transformasjonsmatrisa på mors Canvas API - oCanvas kan definitivt lette læringskurve. Anmeldelser



Previous:
Next Page: