Pengantar
Pada tahun 2010, F-i.com dan tim Google Chrome berkolaborasi dalam aplikasi web pendidikan berbasis HTML5 yang disebut 20 Things I Learned about Browsers and the Web (www.20thingsilearned.com). Salah satu ide utama di balik project ini adalah bahwa project sebaiknya disajikan dalam konteks buku. Karena isi buku ini sangat berkaitan dengan teknologi web terbuka, kami merasa penting untuk tetap jujur terhadap hal tersebut dengan menjadikan container itu sendiri sebagai contoh yang memungkinkan kami capai saat ini.
Kami memutuskan bahwa cara terbaik untuk membuat buku terasa seperti di dunia nyata adalah dengan menyimulasikan bagian yang baik dari pengalaman membaca analog sambil tetap memanfaatkan manfaat dunia digital di berbagai bidang seperti navigasi. Banyak upaya dilakukan untuk memberikan penanganan grafis dan interaktif pada alur membaca - terutama bagaimana halaman buku dibalik dari satu halaman ke halaman lainnya.
Memulai
Tutorial ini akan membawa Anda melalui proses pembuatan efek balik halaman sendiri menggunakan elemen kanvas dan berbagai JavaScript. Beberapa kode dasar, seperti deklarasi variabel dan langganan pemroses peristiwa, tidak disertakan dalam cuplikan dalam artikel ini, jadi ingatlah untuk mereferensikan contoh yang berfungsi.
Sebelum memulai, sebaiknya lihat demo agar Anda tahu apa yang ingin kami buat.
Markup
Penting untuk diingat bahwa apa yang kita gambar di kanvas tidak dapat diindeks oleh mesin telusur, yang dipilih oleh pengunjung, atau ditemukan oleh penelusuran dalam browser. Karena alasan itu, konten yang akan kita kerjakan ditempatkan langsung di DOM, lalu dimanipulasi oleh JavaScript jika tersedia. Markup yang diperlukan untuk konversi ini minimal:
<div id='book'>
<canvas id='pageflip-canvas'></canvas>
<div id='pages'>
<section>
<div> <!-- Any type of contents here --> </div>
</section>
<!-- More <section>s here -->
</div>
</div>
Kita memiliki satu elemen penampung utama untuk buku, yang berisi
halaman buku yang berbeda dan elemen canvas
yang akan
menggambar pembalikan halaman. Di dalam elemen section
, terdapat wrapper div
untuk konten - kita memerlukannya agar dapat mengubah lebar
halaman tanpa memengaruhi tata letak kontennya. div
memiliki
lebar tetap dan section
disetel untuk menyembunyikan tambahannya, sehingga
lebar section
berfungsi sebagai mask horizontal untuk div
.
Logika
Kode yang diperlukan untuk mendukung pembalikan halaman tidak terlalu kompleks, tetapi cukup luas karena melibatkan banyak grafis yang dihasilkan secara prosedural. Mari kita mulai dengan melihat deskripsi nilai konstan yang akan kita gunakan di seluruh kode.
var BOOK_WIDTH = 830;
var BOOK_HEIGHT = 260;
var PAGE_WIDTH = 400;
var PAGE_HEIGHT = 250;
var PAGE_Y = ( BOOK_HEIGHT - PAGE_HEIGHT ) / 2;
var CANVAS_PADDING = 60;
CANVAS_PADDING
ditambahkan di sekitar kanvas sehingga kita dapat membuat
kertas memanjang ke luar buku saat dibalik. Perhatikan bahwa beberapa
konstanta yang ditentukan di sini juga ditetapkan dalam CSS, jadi jika ingin mengubah ukuran buku, Anda juga perlu mengubah nilainya di sana.
Selanjutnya, kita perlu menentukan objek flip untuk setiap halaman, objek ini akan terus diperbarui saat kita berinteraksi dengan buku untuk mencerminkan status flip saat ini.
// Create a reference to the book container element
var book = document.getElementById( 'book' );
// Grab a list of all section elements (pages) within the book
var pages = book.getElementsByTagName( 'section' );
for( var i = 0, len = pages.length; i < len; i++ ) {
pages[i].style.zIndex = len - i;
flips.push( {
progress: 1,
target: 1,
page: pages[i],
dragging: false
});
}
Pertama, kita perlu memastikan bahwa halaman tersebut telah dilapisi dengan benar dengan
mengatur indeks z dari elemen bagian sehingga halaman pertama
berada di atas dan halaman terakhir berada di bawah. Properti yang paling penting
dari objek balik adalah nilai progress
dan target
.
Parameter ini digunakan untuk menentukan seberapa jauh halaman saat ini
harus dilipat, -1 berarti ke kiri, 0 berarti tengah halaman
buku, dan +1 berarti tepi paling kanan buku.
Setelah memiliki objek balik yang ditentukan untuk setiap halaman, kita perlu mulai merekam dan menggunakan input pengguna untuk memperbarui status flip.
function mouseMoveHandler( event ) {
// Offset mouse position so that the top of the book spine is 0,0
mouse.x = event.clientX - book.offsetLeft - ( BOOK_WIDTH / 2 );
mouse.y = event.clientY - book.offsetTop;
}
function mouseDownHandler( event ) {
// Make sure the mouse pointer is inside of the book
if (Math.abs(mouse.x) < PAGE_WIDTH) {
if (mouse.x < 0 && page - 1 >= 0) {
// We are on the left side, drag the previous page
flips[page - 1].dragging = true;
}
else if (mouse.x > 0 && page + 1 < flips.length) {
// We are on the right side, drag the current page
flips[page].dragging = true;
}
}
// Prevents the text selection
event.preventDefault();
}
function mouseUpHandler( event ) {
for( var i = 0; i < flips.length; i++ ) {
// If this flip was being dragged, animate to its destination
if( flips[i].dragging ) {
// Figure out which page we should navigate to
if( mouse.x < 0 ) {
flips[i].target = -1;
page = Math.min( page + 1, flips.length );
}
else {
flips[i].target = 1;
page = Math.max( page - 1, 0 );
}
}
flips[i].dragging = false;
}
}
Fungsi mouseMoveHandler
memperbarui objek mouse
sehingga kita
selalu berusaha menuju lokasi kursor terbaru.
Di mouseDownHandler
, kita mulai dengan memeriksa apakah mouse ditekan
ke bawah di halaman kiri atau kanan sehingga kita mengetahui arah
mana yang ingin kita mulai. Kita juga memastikan bahwa
ada halaman lain di arah itu karena kita mungkin berada di halaman
pertama atau terakhir. Jika opsi balik yang valid tersedia setelah pemeriksaan ini,
kita akan menetapkan flag dragging
dari objek flip yang sesuai ke true
.
Setelah mencapai mouseUpHandler
, kita akan melalui semua flips
dan memeriksa apakah ada yang ditandai sebagai dragging
dan sekarang seharusnya
dirilis. Saat flip dirilis, kita menetapkan nilai targetnya agar cocok dengan
sisi yang harus dibalik, bergantung pada posisi mouse saat ini.
Nomor halaman juga diperbarui untuk mencerminkan navigasi ini.
Rendering
Setelah sebagian besar logika diterapkan, kita akan membahas cara
merender kertas lipat ke elemen kanvas. Sebagian besar hal ini terjadi
di dalam fungsi render()
, yang dipanggil 60 kali
per detik untuk mengupdate dan menggambar status saat ini dari semua balik yang aktif.
function render() {
// Reset all pixels in the canvas
context.clearRect( 0, 0, canvas.width, canvas.height );
for( var i = 0, len = flips.length; i < len; i++ ) {
var flip = flips[i];
if( flip.dragging ) {
flip.target = Math.max( Math.min( mouse.x / PAGE_WIDTH, 1 ), -1 );
}
// Ease progress towards the target value
flip.progress += ( flip.target - flip.progress ) * 0.2;
// If the flip is being dragged or is somewhere in the middle
// of the book, render it
if( flip.dragging || Math.abs( flip.progress ) < 0.997 ) {
drawFlip( flip );
}
}
}
Sebelum mulai merender flips
, kita mereset
kanvas dengan menggunakan metode clearRect(x,y,w,h)
. Membersihkan seluruh kanvas
akan menghabiskan biaya performa yang besar dan akan jauh lebih efisien
untuk membersihkan hanya wilayah yang kita gambar. Agar tutorial ini tetap
tetap sesuai topik, kita akan membiarkan seluruh kanvas
dibersihkan.
Jika balik ditarik, kita akan mengupdate nilai target
-nya agar sesuai dengan
posisi mouse, tetapi pada skala -1 sampai 1, bukan piksel yang sebenarnya.
Kita juga menambahkan progress
dengan sepersekian jarak ke
target
, ini akan menghasilkan perkembangan balik yang lancar dan animasi
karena diperbarui di setiap frame.
Karena kita akan memeriksa semua flips
di setiap frame, kita perlu
memastikan bahwa kita hanya menggambar ulang yang aktif. Jika balik tidak
sangat dekat dengan tepi buku (dalam 0,3% dari BOOK_WIDTH
), atau jika
ditandai sebagai dragging
, maka dianggap aktif.
Setelah semua logika berada, kita perlu menggambar representasi
grafis sebuah membalik, bergantung pada statusnya saat ini. Saatnya untuk
melihat bagian pertama fungsi drawFlip(flip)
.
// Determines the strength of the fold/bend on a 0-1 range
var strength = 1 - Math.abs( flip.progress );
// Width of the folded paper
var foldWidth = ( PAGE_WIDTH * 0.5 ) * ( 1 - flip.progress );
// X position of the folded paper
var foldX = PAGE_WIDTH * flip.progress + foldWidth;
// How far outside of the book the paper is bent due to perspective
var verticalOutdent = 20 * strength;
// The maximum widths of the three shadows used
var paperShadowWidth = (PAGE_WIDTH*0.5) * Math.max(Math.min(1 - flip.progress, 0.5), 0);
var rightShadowWidth = (PAGE_WIDTH*0.5) * Math.max(Math.min(strength, 0.5), 0);
var leftShadowWidth = (PAGE_WIDTH*0.5) * Math.max(Math.min(strength, 0.5), 0);
// Mask the page by setting its width to match the foldX
flip.page.style.width = Math.max(foldX, 0) + 'px';
Bagian kode ini dimulai dengan menghitung sejumlah variabel visual yang perlu kita gambar lipatan secara realistis. Nilai progress
dari balik yang kita gambar memainkan peran besar di sini, karena di situlah kita ingin lipatan halaman muncul. Untuk menambah kedalaman efek membalik
halaman, kita membuat kertas memanjang ke luar tepi
atas dan bawah buku, efek ini berada pada puncaknya saat membalik mendekati
belakang buku.
Sekarang setelah semua nilai sudah disiapkan, yang tersisa hanyalah menggambar kertas!
context.save();
context.translate( CANVAS_PADDING + ( BOOK_WIDTH / 2 ), PAGE_Y + CANVAS_PADDING );
// Draw a sharp shadow on the left side of the page
context.strokeStyle = `rgba(0,0,0,`+(0.05 * strength)+`)`;
context.lineWidth = 30 * strength;
context.beginPath();
context.moveTo(foldX - foldWidth, -verticalOutdent * 0.5);
context.lineTo(foldX - foldWidth, PAGE_HEIGHT + (verticalOutdent * 0.5));
context.stroke();
// Right side drop shadow
var rightShadowGradient = context.createLinearGradient(foldX, 0,
foldX + rightShadowWidth, 0);
rightShadowGradient.addColorStop(0, `rgba(0,0,0,`+(strength*0.2)+`)`);
rightShadowGradient.addColorStop(0.8, `rgba(0,0,0,0.0)`);
context.fillStyle = rightShadowGradient;
context.beginPath();
context.moveTo(foldX, 0);
context.lineTo(foldX + rightShadowWidth, 0);
context.lineTo(foldX + rightShadowWidth, PAGE_HEIGHT);
context.lineTo(foldX, PAGE_HEIGHT);
context.fill();
// Left side drop shadow
var leftShadowGradient = context.createLinearGradient(
foldX - foldWidth - leftShadowWidth, 0, foldX - foldWidth, 0);
leftShadowGradient.addColorStop(0, `rgba(0,0,0,0.0)`);
leftShadowGradient.addColorStop(1, `rgba(0,0,0,`+(strength*0.15)+`)`);
context.fillStyle = leftShadowGradient;
context.beginPath();
context.moveTo(foldX - foldWidth - leftShadowWidth, 0);
context.lineTo(foldX - foldWidth, 0);
context.lineTo(foldX - foldWidth, PAGE_HEIGHT);
context.lineTo(foldX - foldWidth - leftShadowWidth, PAGE_HEIGHT);
context.fill();
// Gradient applied to the folded paper (highlights & shadows)
var foldGradient = context.createLinearGradient(
foldX - paperShadowWidth, 0, foldX, 0);
foldGradient.addColorStop(0.35, `#fafafa`);
foldGradient.addColorStop(0.73, `#eeeeee`);
foldGradient.addColorStop(0.9, `#fafafa`);
foldGradient.addColorStop(1.0, `#e2e2e2`);
context.fillStyle = foldGradient;
context.strokeStyle = `rgba(0,0,0,0.06)`;
context.lineWidth = 0.5;
// Draw the folded piece of paper
context.beginPath();
context.moveTo(foldX, 0);
context.lineTo(foldX, PAGE_HEIGHT);
context.quadraticCurveTo(foldX, PAGE_HEIGHT + (verticalOutdent * 2),
foldX - foldWidth, PAGE_HEIGHT + verticalOutdent);
context.lineTo(foldX - foldWidth, -verticalOutdent);
context.quadraticCurveTo(foldX, -verticalOutdent * 2, foldX, 0);
context.fill();
context.stroke();
context.restore();
Metode translate(x,y)
API kanvas digunakan untuk mengimbangi
sistem koordinasi sehingga kita dapat menggambar flip halaman dengan bagian atas
tulang belakang yang bertindak sebagai posisi 0,0. Perhatikan bahwa kita juga perlu melakukan save()
matriks transformasi kanvas saat ini dan restore()
ke matriks tersebut saat kita selesai menggambar.
foldGradient
adalah elemen yang akan kita isi dengan bentuk kertas lipat untuk
memberikan sorotan dan bayangan yang realistis. Kita juga menambahkan garis yang sangat tipis
di sekitar gambar kertas sehingga kertas tidak hilang saat
diberi latar belakang terang.
Yang tersisa sekarang adalah menggambar bentuk kertas lipat menggunakan properti
yang telah kita tentukan di atas. Sisi kiri dan kanan kertas digambar
sebagai garis lurus, sedangkan sisi atas dan bawah dilengkungkan untuk menimbulkan
rasa kertas lipatan yang tertekuk. Kekuatan lekukan kertas ini
ditentukan oleh nilai verticalOutdent
.
Selesai. Anda kini telah memiliki navigasi balik halaman yang berfungsi penuh.
Demo Membalik Halaman
Efek {i>page flip<i} adalah tentang mengomunikasikan nuansa interaktif yang tepat sehingga melihat gambarnya tidak benar-benar tepat.
Langkah Berikutnya
Ini hanya salah satu contoh hal yang dapat dicapai dengan memanfaatkan fitur HTML5 seperti elemen kanvas. Sebaiknya lihat pengalaman buku yang lebih baik dari teknik ini sebagai kutipan di: www.20thingsilearned.com. Di sana, Anda akan melihat bagaimana membalik halaman dapat diterapkan dalam aplikasi nyata dan seberapa kuatnya saat dipasangkan dengan fitur HTML5 lainnya.
Referensi
- Spesifikasi Canvas API