Latar Belakang Saya
<canvas>
mulai saya ketahui pada tahun 2006 saat Firefox v2.0 dirilis. Artikel tentang Ajaxian, yang menjelaskan matriks transformasi, menginspirasi saya untuk membuat aplikasi web <canvas>
pertama saya; Color Sphere (2007). Yang membenamkan saya ke dunia warna, dan primitif grafis; menginspirasi pembuatan Sketchpad (2007-2008) dalam upaya untuk membuat aplikasi "lebih baik daripada Paint" di browser.
Eksperimen ini pada akhirnya menghasilkan startup Mugtug dengan
teman lama saya, Charles Pritchard. Kita sedang mengembangkan Darkroom di HTML5 <canvas>
. Darkroom
adalah aplikasi berbagi foto non-destruktif, yang menggabungkan kekuatan filter berbasis piksel dengan
tipografi dan gambar berbasis vektor.
Pengantar
<canvas>
memberi programmer JavaScript kontrol penuh atas warna,
vektor, dan piksel di layar mereka - tampilan visual monitor.
Contoh berikut menangani satu area di <canvas>
yang belum mendapat perhatian
banyak; membuat efek teks. Variasi efek teks
yang dapat dibuat di <canvas>
sangat luas seperti yang dapat Anda bayangkan - demo ini
mencakup subbagian tentang apa yang mungkin dilakukan. Meskipun kita menangani
"teks" dalam artikel ini, metode ini dapat diterapkan ke objek vektor apa pun;
membuat visual yang menarik dalam game, dan aplikasi lainnya:
- Bayangan Teks di
<canvas>
.
Efek teks - seperti CSS di
<canvas>
membuat mask pemangkasan, menemukan metrik di<canvas>
, dan menggunakan properti bayangan. - Efek rantai pelangi neon, refleksi zebra.
- Efek teks seperti Photoshop dalam
<canvas>
contoh penggunaan globalCompositeOperation, createLinearGradient, createPattern. - Bayangan dalam dan luar di
<canvas>
- Mengungkapkan fitur yang kurang dikenal; menggunakan winding searah jarum jam vs. berlawanan arah jarum jam untuk membuat inversi drop-shadow (inner-shadow).
- Ruang angkasa - efek generatif. Efek teks
- berbasis generatif di
<canvas>
menggunakan siklus warna hsl() danwindow.requestAnimationFrame
untuk menciptakan nuansa gerakan.
Bayangan Teks di Canvas
Salah satu tambahan favorit saya pada spesifikasi CSS3 (beserta border-radius, web-gradient, dan lainnya)
adalah kemampuan untuk membuat bayangan. Penting untuk menyadari perbedaan
antara bayangan CSS dan <canvas>
, khususnya:
CSS menggunakan dua metode; box-shadow untuk elemen kotak, seperti div, span, dan sebagainya; serta text-shadow untuk konten teks.
<canvas>
memiliki satu jenis bayangan; digunakan untuk semua objek
vektor; ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.strokeText, dan sebagainya.
Untuk membuat bayangan di <canvas>
, gunakan empat properti berikut:
- ctx.shadowColor = "red" // string
- Warna bayangan; RGB, RGBA, HSL, HEX, dan input lainnya valid.
- ctx.shadowOffsetX = 0; // bilangan bulat
- Jarak horizontal bayangan, sehubungan dengan teks.
- ctx.shadowOffsetY = 0; // bilangan bulat
- Jarak vertikal bayangan, sehubungan dengan teks.
- ctx.shadowBlur = 10; // bilangan bulat
- Efek pemburaman pada bayangan, semakin besar nilainya, semakin besar pemburaman.
Untuk memulai, mari kita lihat bagaimana <canvas>
dapat mengemulasikan efek CSS.
Menelusuri gambar Google untuk "css text-shadow" akan menghasilkan beberapa demo
yang bagus untuk kita tiru; Line25,
dan Stereoscopic, serta
Shadow 3D.
Efek 3D stereoskopik (lihat Gambar anaglif untuk mengetahui informasi selengkapnya) adalah contoh baris kode sederhana yang sangat berguna. Dengan baris CSS berikut, kita dapat membuat ilusi kedalaman saat dilihat dengan kacamata 3D merah/sian (seperti yang diberikan kepada Anda di bioskop 3D):
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
Ada dua hal yang perlu diperhatikan saat mengonversi string ini ke <canvas>
:
- Tidak ada shadow-blur (nilai ketiga), jadi tidak ada alasan untuk benar-benar menjalankan shadow, karena fillText akan membuat hasil yang sama:
var text = "Hello world!"
ctx.fillStyle = "#000"
ctx.fillText(text, -7, 0);
ctx.fillStyle = "red"
ctx.fillText(text, 0, 0);
ctx.fillStyle = "cyan"
ctx.fillText(text, 7, 0);</pre>
- EM tidak didukung di
<canvas>
sehingga harus dikonversi ke PX. Kita dapat menemukan rasio konversi untuk mengonversi antara PT, PC, EM, EX, PX, dan sebagainya dengan membuat elemen dengan properti font yang sama di DOM, dan menetapkan lebar ke format yang akan diukur; atau misalnya, untuk mengambil konversi EM -> PX, kita akan mengukur elemen DOM dengan "height: 1em", offsetHeight yang dihasilkan akan berupa jumlah PX dalam setiap EM.
var font = "20px sans-serif"
var d = document.createElement("span");
d.style.cssText = "font: " + font + " height: 1em; display: block"
// the value to multiply PX 's by to convert to EM 's
var EM2PX = 1 / d.offsetHeight;</pre>
Mencegah perkalian alfa
Dalam contoh yang lebih kompleks, seperti efek Neon yang ditemukan di Line25, properti shadowBlur harus digunakan untuk mengemulasikan efek dengan benar. Karena
efek Neon mengandalkan beberapa bayangan, kita mengalami masalah; di <canvas>
,
setiap objek vektor hanya dapat memiliki satu bayangan. Jadi, untuk menggambar beberapa
bayangan, Anda harus menggambar beberapa versi teks di atasnya.
Hal ini menghasilkan perkalian alfa, dan akhirnya tepi bergerigi.
Saya mencoba menjalankan ctx.fillStyle = "rgba(0,0,0,0)"
atau "transparent"
untuk menyembunyikan teks, sekaligus menampilkan bayangan… Namun, upaya ini sia-sia;
karena bayangan adalah perkalian alfa fillStyle, bayangan tidak akan pernah
lebih buram daripada fillStyle.
Untungnya, ada cara untuk mengatasinya, kita dapat menggambar offset bayangan dari teks, menjaganya tetap terpisah (sehingga tidak tumpang-tindih), dan dengan demikian menyembunyikan teks dari sisi layar:
var text = "Hello world!"
var blur = 10;
var width = ctx.measureText(text).width + blur * 2;
ctx.textBaseline = "top"
ctx.shadowColor = "#000"
ctx.shadowOffsetX = width;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -width, 0);
Memotong di sekitar blok teks
Untuk membersihkannya sedikit, kita dapat mencegah fillText digambar
di awal (sambil mengizinkan bayangan digambar) dengan menambahkan jalur pemangkasan.
Untuk membuat jalur pemangkasan di sekitar teks, kita perlu mengetahui
tinggi teks (disebut "em-height" yang secara historis merupakan tinggi
huruf "M" pada mesin cetak), dan lebar teks.
Kita bisa mendapatkan lebar menggunakan ctx.measureText().width
, tetapi
ctx.measureText().height
tidak ada.
Untungnya, melalui peretasan CSS (lihat Metrik Tipografi
untuk mengetahui cara lain memperbaiki implementasi lama <canvas>
menggunakan pengukuran CSS),
kita dapat menemukan tinggi teks melalui pengukuran offsetHeight
<span>
dengan properti font yang sama:
var d = document.createElement("span");
d.font = "20px arial"
d.textContent = "Hello world!"
var emHeight = d.offsetHeight;
Dari sana, kita dapat membuat persegi panjang untuk digunakan sebagai jalur pemangkasan; yang melingkupi "bayangan" sekaligus menghapus bentuk dummy.
ctx.rect(0, 0, width, emHeight);
ctx.clip();
Menggabungkan semuanya, dan mengoptimalkan saat kita melakukannya - jika bayangan tidak memiliki pemburaman, fillText dapat digunakan untuk efek yang sama, sehingga kita tidak perlu menyiapkan mask pemangkasan:
var width = ctx.measureText(text).width;
var style = shadowStyles[text];
// add a background to the current effect
ctx.fillStyle = style.background;
ctx.fillRect(0, offsetY, ctx.canvas.width, textHeight - 1)
// parse text-shadows from css
var shadows = parseShadow(style.shadow);
// loop through the shadow collection
var n = shadows.length; while(n--) {
var shadow = shadows[n];
var totalWidth = width + shadow.blur * 2;
ctx.save();
ctx.beginPath();
ctx.rect(offsetX - shadow.blur, offsetY, offsetX + totalWidth, textHeight);
ctx.clip();
if (shadow.blur) { // just run shadow (clip text)
ctx.shadowColor = shadow.color;
ctx.shadowOffsetX = shadow.x + totalWidth;
ctx.shadowOffsetY = shadow.y;
ctx.shadowBlur = shadow.blur;
ctx.fillText(text, -totalWidth + offsetX, offsetY + metrics.top);
} else { // just run pseudo-shadow
ctx.fillStyle = shadow.color;
ctx.fillText(text, offsetX + (shadow.x||0), offsetY - (shadow.y||0) + metrics.top);
}
ctx.restore();
}
// drawing the text in the foreground
if (style.color) {
ctx.fillStyle = style.color;
ctx.fillText(text, offsetX, offsetY + metrics.top);
}
// jump to next em-line
ctx.translate(0, textHeight);
Karena Anda tidak ingin memasukkan semua perintah <canvas>
ini
secara manual, saya telah menyertakan parser text-shadow sederhana dalam sumber demo;
dengan cara ini, Anda dapat memberinya perintah CSS dan membuatnya menghasilkan perintah <canvas>
.
Sekarang, elemen <canvas>
kita memiliki berbagai gaya yang dapat diikat.
Efek bayangan yang sama ini dapat digunakan pada objek vektor apa pun, mulai dari WebFonts hingga
bentuk kompleks yang diimpor dari SVG, hingga bentuk vektor generatif, dan sebagainya.
Jeda (tantangan pada pendorongan piksel)
Saat menulis bagian artikel ini, contoh Stereoskopik membuat saya penasaran. Seberapa sulitkah membuat efek layar film 3D menggunakan <canvas>
dan dua gambar yang diambil dari perspektif yang sedikit berbeda? Sepertinya, tidak terlalu sulit. Kernel berikut menggabungkan saluran merah gambar pertama (data) dengan saluran cyan gambar kedua (data2):
data[i] = data[i] * 255 / 0xFF;
data[i+1] = 255 * data2[i+1] / 0xFF;
data[i+2] = 255 * data2[i+2] / 0xFF;
Sekarang, seseorang hanya perlu menempelkan dua iPhone ke dahinya dengan selotip, mengklik "rekam video" secara bersamaan, dan kita dapat membuat film 3D sendiri di HTML5. Ada sukarelawan?
Efek rantai pelangi neon, refleksi zebra
Mengaitkan beberapa efek di <canvas>
dapat dilakukan dengan mudah, tetapi pengetahuan dasar
tentang globalCompositeOperation (GCO) diperlukan. Untuk membandingkan
operasi dengan GIMP (atau Photoshop): ada 12 GCO di <canvas>
darker, dan lighter dapat dianggap sebagai mode perpaduan lapisan;
10 operasi lainnya diterapkan ke lapisan sebagai mask alfa (satu lapisan
menghapus piksel lapisan lainnya). globalCompositeOperation mengikat "lapisan"
(atau dalam kasus ini, string kode) bersama-sama, menggabungkannya dengan cara baru dan menarik:
Diagram globalCompositeOperation menunjukkan cara kerja mode GCO; diagram ini menggunakan sebagian besar spektrum warna dan beberapa tingkat transparansi alfa untuk melihat secara mendetail apa yang akan terjadi. Sebaiknya lihat referensi globalCompositeOperation Mozilla untuk deskripsi tekstual. Untuk riset lebih lanjut, Anda dapat mempelajari cara kerja operasi di Compositing Digital Images Porter Duff.
Mode favorit saya adalah globalCompositeOperation="lighter". Lebih ringan mencampur
piksel yang ditambahkan dengan mirip dengan bagaimana cahaya bercampur; saat cahaya merah, hijau, dan putih pada
intensitas penuh, kita melihat cahaya putih. Fitur ini menarik untuk dimainkan, terutama jika <canvas>
disetel ke globalAlpha yang rendah; memungkinkan kontrol yang lebih baik,
dan tepi yang lebih halus. Lighter telah digunakan untuk banyak hal, favorit saya baru-baru ini adalah
pencipta latar belakang desktop HTML5 yang dapat ditemukan di http://weavesilk.com/.
Salah satu demo saya, Breathing Galaxies (JS1k),
juga menggunakan mode yang lebih ringan - menggambar pola dari dua contoh ini, Anda mulai
melihat efek yang dihasilkan mode ini.
Penanganan browser globalCompositeOperation.
Efek Jitter Neon-Pelangi
Dalam demo berikut, kita akan mendapatkan neon-rainbow-glow seperti Photoshop dengan garis luar yang bergetar, dengan merangkai efek bersama menggunakan globalCompositeOperation (source-in, lebih terang, dan lebih gelap).
Demo ini adalah progres dari demo "Text-Shadows in <canvas>
",
menggunakan strategi yang sama dalam memisahkan bayangan dari teks (lihat bagian sebelumnya):
function neonLightEffect() {
var text = "alert('"+String.fromCharCode(0x2665)+"')";
var font = "120px Futura, Helvetica, sans-serif";
var jitter = 25; // the distance of the maximum jitter
var offsetX = 30;
var offsetY = 70;
var blur = getBlurValue(100);
// save state
ctx.save();
ctx.font = font;
// calculate width + height of text-block
var metrics = getMetrics(text, font);
// create clipping mask around text-effect
ctx.rect(offsetX - blur/2, offsetY - blur/2,
offsetX + metrics.width + blur, metrics.height + blur);
ctx.clip();
// create shadow-blur to mask rainbow onto (since shadowColor doesn't accept gradients)
ctx.save();
ctx.fillStyle = "#fff";
ctx.shadowColor = "rgba(0,0,0,1)";
ctx.shadowOffsetX = metrics.width + blur;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -metrics.width + offsetX - blur, offsetY + metrics.top);
ctx.restore();
// create the rainbow linear-gradient
var gradient = ctx.createLinearGradient(0, 0, metrics.width, 0);
gradient.addColorStop(0, "rgba(255, 0, 0, 1)");
gradient.addColorStop(0.15, "rgba(255, 255, 0, 1)");
gradient.addColorStop(0.3, "rgba(0, 255, 0, 1)");
gradient.addColorStop(0.5, "rgba(0, 255, 255, 1)");
gradient.addColorStop(0.65, "rgba(0, 0, 255, 1)");
gradient.addColorStop(0.8, "rgba(255, 0, 255, 1)");
gradient.addColorStop(1, "rgba(255, 0, 0, 1)");
// change composite so source is applied within the shadow-blur
ctx.globalCompositeOperation = "source-atop";
// apply gradient to shadow-blur
ctx.fillStyle = gradient;
ctx.fillRect(offsetX - jitter/2, offsetY,
metrics.width + offsetX, metrics.height + offsetY);
// change composite to mix as light
ctx.globalCompositeOperation = "lighter";
// multiply the layer
ctx.globalAlpha = 0.7
ctx.drawImage(ctx.canvas, 0, 0);
ctx.drawImage(ctx.canvas, 0, 0);
ctx.globalAlpha = 1
// draw white-text ontop of glow
ctx.fillStyle = "rgba(255,255,255,0.95)";
ctx.fillText(text, offsetX, offsetY + metrics.top);
// created jittered stroke
ctx.lineWidth = 0.80;
ctx.strokeStyle = "rgba(255,255,255,0.25)";
var i = 10; while(i--) {
var left = jitter / 2 - Math.random() * jitter;
var top = jitter / 2 - Math.random() * jitter;
ctx.strokeText(text, left + offsetX, top + offsetY + metrics.top);
}
ctx.strokeStyle = "rgba(0,0,0,0.20)";
ctx.strokeText(text, offsetX, offsetY + metrics.top);
ctx.restore();
};
Efek Pantulan Zebra
Efek Zebra Reflection terinspirasi dari referensi yang sangat baik dari WebDesignerWall tentang cara mempercantik halaman dengan CSS. Ini membawa ide sedikit lebih jauh, menciptakan "refleksi" untuk teks - seperti apa yang mungkin Anda lihat di iTunes. Efek ini menggabungkan fillColor (putih), createPattern (zebra.png), dan linearGradient (shine); hal ini mengilustrasikan kemampuan untuk menerapkan beberapa jenis isi ke setiap objek vektor:
function sleekZebraEffect() {
// inspired by - http://www.webdesignerwall.com/demo/css-gradient-text/
var text = "Sleek Zebra...";
var font = "100px Futura, Helvetica, sans-serif";
// save state
ctx.save();
ctx.font = font;
// getMetrics calculates:
// width + height of text-block
// top + middle + bottom baseline
var metrics = getMetrics(text, font);
var offsetRefectionY = -20;
var offsetY = 70;
var offsetX = 60;
// throwing a linear-gradient in to shine up the text
var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
gradient.addColorStop(0.1, '#000');
gradient.addColorStop(0.35, '#fff');
gradient.addColorStop(0.65, '#fff');
gradient.addColorStop(1.0, '#000');
ctx.fillStyle = gradient
ctx.fillText(text, offsetX, offsetY + metrics.top);
// draw reflected text
ctx.save();
ctx.globalCompositeOperation = "source-over";
ctx.translate(0, metrics.height + offsetRefectionY)
ctx.scale(1, -1);
ctx.font = font;
ctx.fillStyle = "#fff";
ctx.fillText(text, offsetX, -metrics.height - offsetY + metrics.top);
ctx.scale(1, -1);
// cut the gradient out of the reflected text
ctx.globalCompositeOperation = "destination-out";
var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
gradient.addColorStop(0.0, 'rgba(0,0,0,0.65)');
gradient.addColorStop(1.0, '#000');
ctx.fillStyle = gradient;
ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height);
// restore back to original transform state
ctx.restore();
// using source-atop to allow the transparent .png to show through to the gradient
ctx.globalCompositeOperation = "source-atop";
// creating pattern from <image> sourced.
ctx.fillStyle = ctx.createPattern(image, 'repeat');
// fill the height of two em-boxes, to encompass both normal and reflected state
ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height * 2);
ctx.restore();
};
Bayangan dalam/luar di Canvas
Spesifikasi <canvas>
tidak membahas subjek bayangan "dalam"
vs. "luar". Bahkan, pada tampilan pertama, Anda mungkin mengharapkan
bayangan "dalam" tidak didukung. Bukan itu masalahnya.
Hanya sedikit lebih sulit untuk mengaktifkannya ;) Seperti yang diusulkan dalam postingan terbaru
dari F1LT3R,
Anda dapat membuat bayangan dalam menggunakan properti unik dari aturan winding
searah jarum jam vs. berlawanan arah jarum jam. Untuk melakukannya, Anda membuat "bayangan dalam" dengan menggambar persegi panjang penampung, lalu, menggunakan aturan winding yang berlawanan, menggambar bentuk potongan - membuat inversi bentuk.
Contoh berikut memungkinkan bayangan dalam dan fillStyle ditata gayanya dengan warna+gradien+pola secara bersamaan. Anda dapat menentukan rotasi pola secara terpisah; perhatikan garis zebra kini saling tegak lurus. Mask pemangkasan dengan ukuran bounding-box digunakan sehingga tidak memerlukan penampung super besar untuk melampirkan bentuk potongan - meningkatkan kecepatan dengan mencegah bagian bayangan yang tidak perlu diproses.
function innerShadow() {
function drawShape() { // draw anti-clockwise
ctx.arc(0, 0, 100, 0, Math.PI * 2, true); // Outer circle
ctx.moveTo(70, 0);
ctx.arc(0, 0, 70, 0, Math.PI, false); // Mouth
ctx.moveTo(-20, -20);
ctx.arc(30, -30, 10, 0, Math.PI * 2, false); // Left eye
ctx.moveTo(140, 70);
ctx.arc(-20, -30, 10, 0, Math.PI * 2, false); // Right eye
};
var width = 200;
var offset = width + 50;
var innerColor = "rgba(0,0,0,1)";
var outerColor = "rgba(0,0,0,1)";
ctx.translate(150, 170);
// apply inner-shadow
ctx.save();
ctx.fillStyle = "#000";
ctx.shadowColor = innerColor;
ctx.shadowBlur = getBlurValue(120);
ctx.shadowOffsetX = -15;
ctx.shadowOffsetY = 15;
// create clipping path (around blur + shape, preventing outer-rect blurring)
ctx.beginPath();
ctx.rect(-offset/2, -offset/2, offset, offset);
ctx.clip();
// apply inner-shadow (w/ clockwise vs. anti-clockwise cutout)
ctx.beginPath();
ctx.rect(-offset/2, -offset/2, offset, offset);
drawShape();
ctx.fill();
ctx.restore();
// cutout temporary rectangle used to create inner-shadow
ctx.globalCompositeOperation = "destination-out";
ctx.fill();
// prepare vector paths
ctx.beginPath();
drawShape();
// apply fill-gradient to inner-shadow
ctx.save();
ctx.globalCompositeOperation = "source-in";
var gradient = ctx.createLinearGradient(-offset/2, 0, offset/2, 0);
gradient.addColorStop(0.3, '#ff0');
gradient.addColorStop(0.7, '#f00');
ctx.fillStyle = gradient;
ctx.fill();
// apply fill-pattern to inner-shadow
ctx.globalCompositeOperation = "source-atop";
ctx.globalAlpha = 1;
ctx.rotate(0.9);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fill();
ctx.restore();
// apply fill-gradient
ctx.save();
ctx.globalCompositeOperation = "destination-over";
var gradient = ctx.createLinearGradient(-offset/2, -offset/2, offset/2, offset/2);
gradient.addColorStop(0.1, '#f00');
gradient.addColorStop(0.5, 'rgba(255,255,0,1)');
gradient.addColorStop(1.0, '#00f');
ctx.fillStyle = gradient
ctx.fill();
// apply fill-pattern
ctx.globalCompositeOperation = "source-atop";
ctx.globalAlpha = 0.2;
ctx.rotate(-0.4);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fill();
ctx.restore();
// apply outer-shadow (color-only without temporary layer)
ctx.globalCompositeOperation = "destination-over";
ctx.shadowColor = outerColor;
ctx.shadowBlur = 40;
ctx.shadowOffsetX = 15;
ctx.shadowOffsetY = 10;
ctx.fillStyle = "#fff";
ctx.fill();
};
Dari contoh ini, Anda dapat melihat bahwa dengan menggunakan globalCompositeOperation, kita dapat mengaitkan efek bersama-sama, menghasilkan efek yang lebih rumit (menggunakan masking dan penggabungan). Layarnya adalah tiram Anda ;)
Spaceage—efek generatif
Di <canvas>
, beralih dari karakter unicode 0x2708:
…ke contoh yang diarsir ini:
…dapat dicapai dengan beberapa panggilan ke ctx.strokeText()
dengan lineWidth tipis (0,25),
sambil perlahan mengurangi x-offset dan alfa; memberi elemen vektor kita perasaan gerakan.
Dengan memetakan posisi XY elemen ke gelombang sinus/kosinus, dan beralih warna menggunakan properti HSL, kita dapat membuat efek yang lebih menarik, seperti contoh "bahaya hayati" ini:
HSL: Hue, Saturation, Lightness (1978)
HSL adalah format yang baru didukung dalam spesifikasi CSS3. Jika HEX dirancang untuk komputer, HSL dirancang agar dapat dibaca manusia.
Menggambarkan kemudahan HSL; untuk berputar melalui spektrum warna, kita cukup menambahkan "hue" dari 360; hue dipetakan ke spektrum dalam cara silinder. Kecerahan mengontrol seberapa gelap/terang warna; 0% menunjukkan piksel hitam, sedangkan 100% menunjukkan piksel putih. Saturasi mengontrol seberapa terang atau cerah warna; abu-abu dibuat dengan saturasi 0%, dan warna cerah dibuat menggunakan nilai 100%.
Karena HSL adalah standar terbaru, Anda mungkin ingin terus mendukung browser lama, yang dapat dilakukan melalui konversi ruang warna. Kode berikut menerima objek HSL { H: 360, S: 100, L: 100} dan menghasilkan objek RGB { R: 255, G: 255, B: 255 }. Dari sana, Anda dapat menggunakan nilai tersebut untuk membuat string rgb atau rgba. Untuk informasi lebih mendalam, lihat artikel lengkap Wikipedia tentang HSL.
// HSL (1978) = H: Hue / S: Saturation / L: Lightness
HSL_RGB = function (o) { // { H: 0-360, S: 0-100, L: 0-100 }
var H = o.H / 360,
S = o.S / 100,
L = o.L / 100,
R, G, B, _1, _2;
function Hue_2_RGB(v1, v2, vH) {
if (vH < 0) vH += 1;
if (vH > 1) vH -= 1;
if ((6 * vH) < 1) return v1 + (v2 - v1) * 6 * vH;
if ((2 * vH) < 1) return v2;
if ((3 * vH) < 2) return v1 + (v2 - v1) * ((2 / 3) - vH) * 6;
return v1;
}
if (S == 0) { // HSL from 0 to 1
R = L * 255;
G = L * 255;
B = L * 255;
} else {
if (L < 0.5) {
_2 = L * (1 + S);
} else {
_2 = (L + S) - (S * L);
}
_1 = 2 * L - _2;
R = 255 * Hue_2_RGB(_1, _2, H + (1 / 3));
G = 255 * Hue_2_RGB(_1, _2, H);
B = 255 * Hue_2_RGB(_1, _2, H - (1 / 3));
}
return {
R: R,
G: G,
B: B
};
};
Membuat animasi dengan requestAnimationFrame
Sebelumnya, untuk membuat animasi di JavaScript, ada dua pilihan; setTimeout
,
dan setInterval
.
window.requestAnimationFrame
, adalah standar baru untuk menggantikan
keduanya; menghemat listrik dunia (dan beberapa detak jantung komputer Anda) dengan
memungkinkan browser mengatur animasi berdasarkan resource yang tersedia.
Beberapa fitur penting meliputi:
- Saat pengguna ada dalam frame, animasi dapat melambat atau berhenti sepenuhnya, untuk mencegah penggunaan resource yang tidak diperlukan.
- Ada batas kecepatan frame pada 60 FPS. Alasannya adalah nilai ini jauh di atas tingkat yang dapat dilihat manusia (sebagian besar manusia dengan 30 FPS melihat animasi "lancar").
Pada saat penulisan, awalan khusus penjual diperlukan untuk menggunakan requestAnimationFrame
.
Paul Irish membuat lapisan shim yang memiliki dukungan lintas vendor, di requestAnimationFrame untuk
animasi cerdas:
// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
Lebih jauh lagi, yang lebih ambisius mungkin akan mengaitkannya dengan poly-fill seperti requestAnimationFrame.js (ada beberapa fitur yang harus dikerjakan) yang akan mendukung browser lama lebih jauh, sambil beralih ke standar baru ini.
(function animate() {
var i = 50;
while(i--) {
if (n > endpos) return;
n += definition;
ctx.globalAlpha = (0.5 - (n + startpos) / endpos) * alpha;
if (doColorCycle) {
hue = n + color;
ctx.strokeStyle = "hsl(" + (hue % 360) + ",99%,50%)"; // iterate hue
}
var x = cos(n / cosdiv) * n * cosmult; // cosine
var y = sin(n / sindiv) * n * sinmult; // sin
ctx.strokeText(text, x + xoffset, y + yoffset); // draw rainbow text
}
timeout = window.requestAnimationFrame(animate, 0);
})();
Kode sumber
Dengan dukungan dari seluruh vendor browser, tidak ada keraguan tentang
masa depan <canvas>
yang dapat di-porting ke file yang dapat dieksekusi iPhone/Android/Desktop
menggunakan PhoneGap, atau