Webprogramozás

Window, canvas, API

Horváth Győző
Egyetemi docens
1117 Budapest, Pázmány Péter sétány 1/c., 2.408-as szoba
Tel: (1) 372-2500/8469
horvath.gyozo@inf.elte.hu

Ismétlés

Ismétlés

  • JavaScript nyelvi elemei
  • DOM programozás
  • Eseménykezelés részletei
  • JavaScript beépített objektumai
  • Kódszervezés
  • Űrlapok, képek, táblázatok
  • Progresszív fejlesztés

window

Browser Object Model

A böngészőablak

window

  • DOM-ot tartalmazó ablak (fül), JS kontextusa
  • Globális névtér
  • Ablakhoz kapcsolódó funkciók
  • Sok egyéb API-nak a tárolója
  • Browser Object Model (BOM)
  • Referencia

Globális névtér

function hello() {
  console.log('Hello');
}
window.hello()

// Unintentional global variable
function sideEffect() {
    let aVeryBigVariable
    aVeryBigvariable = 12; // window.aVeryBigvariable
}
sideEffect()
console.log(aVeryBigvariable);
console.log(window.aVeryBigvariable);

// strict mode
function sideEffectStrict() {
    "use strict"
    let anotherVeryBigVariable
    anotherVeryBigvariable = 12;
}
sideEffectStrict()
console.log(anotherVeryBigvariable);

Új ablak

  • window.open(), window.close()
  • opener
  • Az új ablak referenciája egy window objektum
var w = window.open('http://www.elte.hu', 'ELTEablak', 'width=800,height=600,scrollbars=yes')
w.document.querySelector(p) // Cross-origin policy!
w.resizeTo(400, 200)
w.opener
w.close()

Location

  • window.location – betöltött oldal URL-je
    • hash, host, hostname, href, origin, pathname, port, protocol, search, username, password
// http://webprogramozas.inf.elte.hu:8080/webfejl2.html?alma=piros#valami
const l = window.location;
l.host;      //"webprogramozas.inf.elte.hu:8080"
l.hostname;  //"webprogramozas.inf.elte.hu"
l.href;      //az egész URL
l.origin;    //"http://webprogramozas.inf.elte.hu:8080"
l.pathname;  //"/webfejl2.html"
l.port;      //"8080"
l.protocol;  //"http:"
l.search;    //"?alma=piros"
l.hash;      //"#valami"

Location

  • Metódusok
    • assign(url)
    • replace(url)
    • reload()
  • Események
    • hashchange
window.location = "http://www.elte.hu";
window.location.href = "http://www.elte.hu";
window.location.assign("http://www.elte.hu");
window.location.replace("http://www.elte.hu");
window.location.reload();

History

Közlekedés

  • window.history
    • back()
    • forward()
    • go(n)
window.history.back();
window.history.forward();
window.history.go(-3);

History

Módosítás

  • window.history.pushState(stateObj, name, url)
  • window.history.replaceState(stateObj, name, url)
  • window.onpopstate esemény
window.onpopstate = function(e) {
  console.log("location: " + document.location + ", state: " + JSON.stringify(e.state));
};

history.pushState({page: 1}, "title 1", "?page=1");
history.pushState({page: 2}, "title 2", "?page=2");
history.replaceState({page: 3}, "title 3", "?page=3");
history.back(); // logs "location: http://example.com/example.html?page=1, state: {"page":1}"
history.back(); // logs "location: http://example.com/example.html, state: null"
history.go(2);  // logs "location: http://example.com/example.html?page=3, state: {"page":3}"

DEMO

Iframe programozás

  • Támogatott
  • Ablak az ablakon belül
  • Szeparált környezet
    • sandboxing
    • aszinkron kommunikáció
  • Elérés
    • window.parent
    • iframe.contentWindow
    • iframe.contentWindow.document / iframe.contentDocument
  • Same Origin Policy

postMessage

  • Felügyelt kommunikáció ablakok között
  • postMessage(), message esemény
<iframe src="http://localhost:8081/window.postmessage.iframe.html"></iframe>
// parent window
window.addEventListener('load', function () {
    const iframe = document.querySelector('iframe')
    iframe.contentWindow.postMessage("message", "http://localhost:8081")
})
window.addEventListener('message', function (e) {
    console.log(e.origin, e.data)
})
// iframe
window.addEventListener('message', function (e) {
    if (e.origin === "http://localhost:8080") {
        console.log(e.data)
        window.parent.postMessage('message back', 'http://localhost:8080')
    }
})

Időzítők

  • setTimeout, clearTimeout
  • setInterval, clearInterval
  • requestAnimationFrame, cancelAnimationFrame
  • requestIdleCallback, cancelIdleCallback

További window tulajdonságok

  • Tulajdonság
    • name
    • fullscreen
  • Események
    • load, unload
    • abort , close
    • contextmenu
    • resize, scroll, select
    • storage
    • copy, cut, paste
  • Metódusok
    • alert, confirm, prompt
    • atob, btoa (base64 kódolás)
    • matchMedia
    • print
    • postMessage
    • stop
    • fetch

Canvas API

Böngészők grafikus lehetőségei

  • CSS
  • Képek
  • SVG (vektorgrafika)
  • Canvas (raszteres grafika)

Canvas

<canvas width="200" height="200"></canvas>
// 2D rajzolói környezet
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')

Alakzatok

  • Téglalap
    • fillRect(x,y,width,height)
    • strokeRect(x,y,width,height)
    • clearRect(x,y,width,height) 
  • Vonalak
    • moveTo(x, y)
    • lineTo(x, y)
  • Útvonalak
    • beginPath()
    • closePath()
    • stroke()
    • fill()
  • Ívek
    • arc(x, y, radius, startAngle, endAngle, anticlockwise)
  • Magasabb rendű görbék
    • quadraticCurveTo(cp1x, cp1y, x, y)
    • bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

Alakzatok példa

ctx.fillRect(5, 5, 20, 100)
ctx.strokeRect(30, 5, 20, 100)

ctx.beginPath()
ctx.rect(110, 5, 20, 100)
ctx.moveTo(130, 5)
ctx.lineTo(160, 35)
ctx.lineTo(130, 65)
ctx.stroke()

ctx.beginPath()
ctx.arc(200, 50, 30, 0, 2 * Math.PI)
ctx.fill()

ctx.beginPath()
ctx.moveTo(5, 200)
ctx.quadraticCurveTo(55, 100, 105, 200)
ctx.closePath()
ctx.stroke()

ctx.beginPath()
ctx.moveTo(105, 200)
ctx.bezierCurveTo(105, 130, 200, 150, 200, 90)
ctx.lineTo(190, 90)
ctx.lineTo(200, 80)
ctx.lineTo(210, 90)
ctx.lineTo(200, 90)
ctx.stroke()

Képek, szövegek

  • Képek forrása
    • meglévő kép felhasználása: <img>
    • memóriabeli kép: const img=document.createElement('img');
    • másik canvas
    • video elem
  • Képek
    • drawImage(image, x, y)
    • drawImage(image, x, y, width, height)
    • drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
  • Szövegek
    • fillText(str, x, y)
    • strokeText(str, x, y)

Stílusok és egyesítés

  • Stílusok és színek
    • fillStyle=color
    • strokeStyle=color
    • globalAlpha=value
    • lineWidth=value
    • Vonalstílusok
    • Gradiensek
    • Mintázatok
    • Árnyékolás
  • Egyesítés
    • globalCompositeOperation=type
  • Vágás
    • útvonal
    • clip()

Példa

const plane = document.createElement('img')
plane.src = 'plane.png'
plane.addEventListener('load', draw)
function draw() {
  const gradSky = ctx.createLinearGradient(200, 0, 200, 300)
  gradSky.addColorStop(0, 'lightblue')
  gradSky.addColorStop(0.5, 'orange')
  gradSky.addColorStop(1, '#444444')
  
  const gradMountain = ctx.createLinearGradient(200, 60, 200, 300)
  gradMountain.addColorStop(0, 'white')
  gradMountain.addColorStop(0.2, 'white')
  gradMountain.addColorStop(0.7, '#5555aa')
  gradMountain.addColorStop(1, 'green')
  
  ctx.fillStyle = gradSky
  ctx.fillRect(0, 0, 400, 300)
  
  ctx.beginPath()
  ctx.fillStyle = gradMountain
  ctx.moveTo(0, 300)
  ctx.lineTo(0, 200)
  ctx.lineTo(100, 100)
  ctx.lineTo(100, 100)
  ctx.lineTo(220, 160)
  ctx.lineTo(330, 60)
  ctx.lineTo(400, 100)
  ctx.lineTo(400, 300)
  ctx.closePath()
  ctx.fill()
  
  ctx.drawImage(plane, 30, 40, 70, 50)
}

Transzformációk

  • Állapotkezelés
    • save()
    • restore()
  • Transzformációk
    • translate(x,y)
    • rotate(angle)
    • scale(x,y)
    • transform(m11,m12,m21,m22,dx,dy)
for (var i = 0; i < 3; i++) {
  for (var j = 0; j < 3; j++) {
    ctx.save();
    ctx.fillStyle = 'rgb(' + (51 * i) + ', ' + (255 - 51 * i) + ', 255)';
    ctx.translate(10 + j * 50, 10 + i * 50);
    ctx.fillRect(0, 0, 25, 25);
    ctx.restore();
  }
}

Animációk

  • Elv: canvas gyors újrarajzolása
    1. Változások rajzolása
    2. Teljes rajzolás
      • canvas törlése
      • rajzolás
  • Modell
    • állapottér (adatok)
    • nézet (rajzolás)

Animációs ciklus

const mainloop = function() {
  window.requestAnimationFrame(mainloop);
  update();
  draw();
};

function update() {
  // Állapottér változtatása
}

function draw() {
  // Állapottér kirajzolása
}

Idő alapú animáció

let lastTime = performance.now()
function mainloop(now = performance.now()) {
    window.requestAnimationFrame(mainloop)
    const dt = (now - lastTime) / 1000
    lastTime = now
    
    update(dt)
    draw()
}
mainloop()
  • v = s / t
    • ds = v * dt
  • a = v / t
    • dv = a * dt

Példa

const box = {
  x: 50, y: 50,
  vx: random(50, 200), vy: random(50, 200),
  width: 30, height: 30,
}
function update(dt) {
  box.x += box.vx * dt
  box.y += box.vy * dt
  if (box.x + box.width > canvas.width) {
    box.x = 2 * (canvas.width - box.width) - box.x
    box.vx *= -1
  }
  if (box.x < 0) {
    box.x *= -1
    box.vx *= -1
  }
  if (box.y + box.height > canvas.height) {
    box.y = 2 * (canvas.height - box.height) - box.y
    box.vy *= -1
  }
  if (box.y < 0) {
    box.y *= -1
    box.vy *= -1
  }
}
function draw() {
  // clear
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  // draw box
  ctx.fillRect(box.x, box.y, box.width, box.height)
}

Egységbe zárás

class Entity {
  constructor(canvas, ctx) {
    this.canvas = canvas
    this.ctx = ctx
  }
  update(dt) {

  }
  draw() {

  }
}

Példa

class Box {
  constructor(canvas, ctx, {x = 50, y = 50, vx = random(50, 200), vy = random(50, 200), width = 30, height = 30} = {}) {
    Object.assign(this, {canvas, ctx, x, y, vx, vy, width, height})
  }
  update(dt) {
    this.x += this.vx * dt
    this.y += this.vy * dt
    if (this.x + this.width > this.canvas.width) {
      this.x = 2 * (this.canvas.width - this.width) - this.x
      this.vx *= -1
    }
    if (this.x < 0) {
      this.x *= -1
      this.vx *= -1
    }
    if (this.y + this.height > this.canvas.height) {
      this.y = 2 * (this.canvas.height - this.height) - this.y
      this.vy *= -1
    }
    if (this.y < 0) {
      this.y *= -1
      this.vy *= -1
    }
  }
  draw() {
    // draw box
    this.ctx.fillRect(this.x, this.y, this.width, this.height)
  }
}

class Game {
  constructor() {
    this.canvas = document.querySelector('canvas')
    this.ctx = this.canvas.getContext('2d')
    this.lastTime = performance.now()
    this.box = new Box(this.canvas, this.ctx)
  }
  start() {
    this.mainloop()
  }
  mainloop = (now = performance.now()) => {
    window.requestAnimationFrame(this.mainloop)
    const dt = (now - this.lastTime) / 1000
    this.lastTime = now

    this.update(dt)
    this.draw()
  }
  update(dt) {
    this.box.update(dt)
  }
  draw() {
    // clear
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
    // box
    this.box.draw()
  }
}
(new Game()).start()

Spritesheet animáció

Spritesheet animáció

const sprite = {
  currentFrame: 0,
  image: new Image(),
  frameCount: 16,
  frameTime: 0.03,
  elapsedTime: 0,
}
function update(dt) {
  sprite.elapsedTime += dt
  if (sprite.elapsedTime > sprite.frameTime) {
    sprite.currentFrame = (sprite.currentFrame + 1) % sprite.frameCount
    sprite.elapsedTime -= sprite.frameTime
  }
}
function draw() {
  // clear
  ctx.clearRect(0, 0, canvas.width, canvas.height)

  // draw sprite
  ctx.drawImage(sprite.image,
    (sprite.currentFrame % 4) * 128, Math.floor(sprite.currentFrame / 4) * 128, 127, 127,
    canvas.width / 2 - 64, canvas.height / 2 - 64, 128, 128);
}
function init() {
  sprite.image.src = 'spritesheet.png'
  sprite.image.addEventListener('load', function () {
    mainloop()
  })
}
init()

Spritesheet mozgás

const sprite = {
  currentFrame: 0,
  image: new Image(),
  frameCount: 16,
  frameTime: 0.03,
  elapsedTime: 0,
  x: 50,
  vx: 150,
  direction: 1,
}
function update(dt) {
  sprite.elapsedTime += dt
  sprite.x += sprite.direction * sprite.vx * dt
  if (sprite.elapsedTime > sprite.frameTime) {
    sprite.currentFrame = (sprite.currentFrame + 1) % sprite.frameCount
    sprite.elapsedTime -= sprite.frameTime
  }
  if (sprite.x + 32 > canvas.width || sprite.x - 32 < 0) {
    sprite.direction *= -1
  }
}
function draw() {
  // clear
  ctx.clearRect(0, 0, canvas.width, canvas.height)

  // draw sprite
  ctx.save()
  ctx.translate(sprite.x, canvas.height / 2 - 32)
  ctx.scale(sprite.direction, 1)
  ctx.drawImage(sprite.image,
    (sprite.currentFrame % 4) * 128, Math.floor(sprite.currentFrame / 4) * 128, 127, 127,
    -32, -32, 64, 64);
  ctx.restore()
}

Kis grafikával…

Interakció

  • isPointInPath()
  • isPointInStroke()
  • Hit regions
    • addHitRegion()
    • removeHitRegion()
    • clearHitRegions()

Canvas 3D

const gl = document.getElementById("canvas").getContext("webgl");
gl.viewport(0, 0, canvas.width, canvas.height);
//...

Inline SVG

<html>
  <svg width="300px" height="300px">
    <defs>
      <linearGradient id="myGradient" x1="0%" y1="100%" x2="100%" y2="0%">
        <stop offset="5%" stop-color="red"></stop>
        <stop offset="95%" stop-color="blue" stop-opacity="0.5"></stop>
      </linearGradient>
    </defs>
    <circle id="myCircle" class="important" cx="50%" cy="50%" r="100"
      fill="url(#myGradient)" onmousedown="alert('hello');"/>
  </svg>
</html>

További API-k

https://developer.mozilla.org/en-US/docs/Web/API

https://developer.mozilla.org/en-US/docs/Web/Media

Tárolás

  • Változók
  • (Süti)
  • HTML 5 Storage (Web Storage)
    • localStorage
    • sessionStorage
  • Web SQL Database (részben támogatott)
  • Indexed Database (IndexedDB)
  • File API (bináris állományok tárolása)

localStorage

  • Kulcs-érték párok gyűjteménye
  • Objektum
//Tárolás
localStorage.alma = 'piros';
localStorage['bármi'] = 42;

//Elővétel (akár újratöltés után)
console.log(localStorage.alma);
console.log(localStorage['bármi']);

//Összetett adatszerkezetek tárolás
const x = [1, 3, 5, 7];
localStorage.setItem('adat', JSON.stringify(x));
const x2 = JSON.parse( localStorage.getItem('adat') );

Audio

  • Hangállományok lejátszása
  • JavaScript API: HTMLAudioElement
    • play(), pause(), …
<audio src="horn3.wav" id="audio1" controls></audio>
// meglévő elem
document.querySelector('audio').play();

// in-memory elem
const audio = document.createElement('audio');
audio.src = 'horn3.wav';
audio.play();

Video

Video

const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const video = document.querySelector('video')
const mainloop = function() {
  window.requestAnimationFrame(mainloop);
  ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
}
mainloop()

Video

let deg = 0;
const mainloop = function() {
  window.requestAnimationFrame(mainloop);

  deg += 0.01
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  ctx.save()
  ctx.translate(canvas.width / 2, canvas.height / 2)
  ctx.scale(0.5, 0.5)
  ctx.rotate(deg)
  ctx.drawImage(video, -canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height)
  ctx.restore()
}

Video

Video

const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const image = document.querySelector('img')
const video = document.querySelector('video')
const red = document.querySelector('#red')
const green = document.querySelector('#green')
const blue = document.querySelector('#blue')

navigator.mediaDevices.getUserMedia({video: true})
.then(function(mediaStream) {
  image.src = mediaStream
  video.srcObject = mediaStream
  video.addEventListener('loadeddata', function () {
    video.play()
    mainloop()
  })
})
.catch(function(err) { console.log(err.name + ": " + err.message) })

const mainloop = function() {
  if (video.paused || video.ended) {
    return
  }
  window.requestAnimationFrame(mainloop);

  ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
  processImage()
}

function processImage() {
  let frame = ctx.getImageData(0, 0, canvas.width, canvas.height);
  let l = frame.data.length / 4;

  for (let i = 0; i < l; i++) {
    let r = frame.data[i * 4 + 0];
    let g = frame.data[i * 4 + 1];
    let b = frame.data[i * 4 + 2];
    if (g > green.valueAsNumber && r > red.valueAsNumber && b > blue.valueAsNumber)
      frame.data[i * 4 + 3] = 0;
  }
  ctx.putImageData(frame, 0, 0);
}

Web workers

  • Valódi többszálúság
  • Kommunikáció: üzenetek/események
//főszál
const worker = new Worker('masik.js');
worker.onmessage = function(e) {
  console.log(e.data);
};
worker.postMessage('valami');

//masik.js
self.onmessage = function(e) {
  self.postMessage("Kapott adat: " + e.data);
};

Drag and drop

  • Vonszolható objektum: draggable="true"
  • dragstart, dragenter, dragover, drop, stb. események
  • event.dataTransfer
    • setData
    • setDragImage
    • effectAllowed
    • dropEffect
  • http://html5demos.com/drag

Drag and drop

Példa

  1. draggable attribútum
  2. dragstart esemény: drag adatok tárolása
  3. dragover esemény: dropzone kijelölése
  4. dragleave esemény: dropzone elhagyása
  5. drop esemény: ejtés
  6. dragend esemény: drag adatok alaphelyzetbe

További HTML 5 JavaScript API

Összefoglalás

  • window
  • Canvas, animációk
  • JS API-k
  • this kontextusa