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 ini akan lebih baik jika disajikan dalam konteks buku. Karena konten buku ini sangat berkaitan dengan teknologi web terbuka, kami merasa penting untuk tetap setia dengan hal tersebut dengan menjadikan penampung itu sendiri sebagai contoh dari apa yang dapat kita capai saat ini dengan teknologi ini.
Kami memutuskan bahwa cara terbaik untuk mendapatkan nuansa buku di dunia nyata adalah dengan menyimulasikan bagian-bagian baik dari pengalaman membaca analog sekaligus tetap memanfaatkan manfaat dunia digital di area seperti navigasi. Banyak upaya yang dilakukan untuk perlakuan grafis dan interaktif pada alur membaca - terutama cara halaman buku dibalik dari satu halaman ke halaman lainnya.
Memulai
Tutorial ini akan memandu Anda dalam proses pembuatan efek membalik halaman Anda sendiri menggunakan elemen kanvas dan banyak JavaScript. Beberapa kode dasar, seperti deklarasi variabel dan langganan pemroses peristiwa, telah dihapus dari cuplikan dalam artikel ini, jadi jangan lupa untuk mereferensikan contoh yang berfungsi.
Sebelum memulai, sebaiknya lihat demo agar Anda tahu apa yang ingin kita buat.
Markup
Penting untuk diingat bahwa apa yang kita gambar di kanvas tidak dapat diindeks oleh mesin telusur, dipilih oleh pengunjung, atau ditemukan oleh penelusuran dalam browser. Oleh karena itu, konten yang akan kita gunakan ditempatkan langsung di DOM, lalu dimanipulasi oleh JavaScript jika tersedia. Markup yang diperlukan untuk 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 pada gilirannya berisi
berbagai halaman buku dan elemen canvas
yang akan kita
gambar halaman yang dibalik. 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 overflow-nya, sehingga lebar
section
berfungsi sebagai mask horizontal untuk div
.
Logika
Kode yang diperlukan untuk mendukung pengalihan halaman tidak terlalu rumit, tetapi cukup luas karena melibatkan banyak grafik yang dihasilkan secara terstruktur. Mari kita mulai dengan melihat deskripsi nilai konstanta 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
memperluas kertas ke luar buku saat membalik. Perhatikan bahwa beberapa
konstanta yang ditentukan di sini juga ditetapkan di CSS, jadi jika Anda ingin mengubah
ukuran buku, Anda juga harus memperbarui nilai 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 halaman dilapisi dengan benar dengan
mengatur z-index elemen bagian sehingga halaman
pertama berada di atas dan halaman terakhir berada di bawah. Properti
paling penting dari objek flip adalah nilai progress
dan target
.
Ini digunakan untuk menentukan seberapa jauh halaman saat ini harus
dilipat, -1 berarti sepenuhnya ke kiri, 0 berarti tepat di tengah
buku, dan +1 berarti tepi paling kanan buku.
Setelah memiliki objek flip yang ditentukan untuk setiap halaman, kita perlu mulai mengambil 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 bekerja menuju lokasi kursor terbaru.
Di mouseDownHandler
, kita mulai dengan memeriksa apakah mouse ditekan
di halaman kiri atau kanan sehingga kita tahu
arah mana yang ingin kita mulai balik. Kita juga memastikan bahwa
halaman lain ada di arah tersebut karena kita mungkin berada di halaman
pertama atau terakhir. Jika opsi flip yang valid tersedia setelah pemeriksaan ini,
kita menetapkan tanda dragging
dari objek flip yang sesuai ke true
.
Setelah mencapai mouseUpHandler
, kita akan memeriksa semua flips
dan melihat apakah ada yang ditandai sebagai dragging
dan sekarang harus
dirilis. Saat flip dilepaskan, 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 kita 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 memperbarui dan menggambar status saat ini dari semua flip 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 menggunakan metode clearRect(x,y,w,h)
. Menghapus seluruh kanvas
akan menimbulkan beban performa yang besar dan akan jauh lebih efisien
jika hanya menghapus area yang kita gambar. Agar tutorial ini tetap sesuai dengan topik, kita akan membiarkannya dengan menghapus seluruh kanvas.
Jika flip sedang ditarik, kita akan memperbarui nilai target
-nya agar cocok dengan
posisi mouse, tetapi pada skala -1 hingga 1, bukan piksel sebenarnya.
Kita juga menambahkan progress
dengan sebagian kecil jarak ke
target
, hal ini akan menghasilkan progres pengalihan yang halus dan animasi
karena diperbarui di setiap frame.
Karena kita akan membahas semua flips
di setiap frame, kita perlu
memastikan bahwa kita hanya menggambar ulang yang aktif. Jika tidak
sangat dekat dengan tepi buku (dalam 0,3% dari BOOK_WIDTH
), atau jika
diberi tanda sebagai dragging
, halaman dianggap aktif.
Setelah semua logika diterapkan, kita perlu menggambar representasi
grafis flip bergantung pada statusnya saat ini. Saatnya
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 kita perlukan untuk menggambar lipatan secara realistis. Nilai
progress
dari flip yang kita gambar memainkan peran besar di sini, karena
di sinilah kita ingin lipatan halaman muncul. Untuk menambahkan kedalaman pada efek
pembalikan halaman, kita membuat kertas memanjang di luar tepi atas dan bawah
buku, efek ini berada di puncaknya saat pembalikan dekat dengan
tulang punggung buku.
Setelah semua nilai 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)
canvas API digunakan untuk mengimbangi
sistem koordinat sehingga kita dapat menggambar flip halaman dengan bagian atas
spine yang berfungsi sebagai posisi 0,0. Perhatikan bahwa kita juga perlu save()
matriks transformasi kanvas saat ini dan restore()
ke kanvas tersebut saat kita selesai menggambar.
foldGradient
adalah yang akan kita isi dengan bentuk kertas yang dilipat
untuk memberinya sorotan dan bayangan yang realistis. Kita juga menambahkan garis yang sangat tipis
di sekitar gambar kertas sehingga kertas tidak menghilang saat ditempatkan
di latar belakang yang terang.
Yang tersisa sekarang adalah menggambar bentuk kertas yang dilipat menggunakan
properti yang kita tentukan di atas. Sisi kiri dan kanan kertas kita digambar
sebagai garis lurus dan sisi atas dan bawah melengkung untuk memberikan kesan lipatan
kertas yang terlipat. Kekuatan tekukan kertas ini
ditentukan oleh nilai verticalOutdent
.
Selesai. Sekarang Anda memiliki navigasi flip halaman yang berfungsi penuh.
Demo Pengalihan Halaman
Efek membalik halaman adalah tentang menyampaikan nuansa interaktivitas yang tepat sehingga melihat gambarnya tidak sepenuhnya adil.
Langkah Berikutnya
Ini hanyalah salah satu contoh yang dapat dicapai dengan memanfaatkan fitur HTML5 seperti elemen kanvas. Sebaiknya lihat pengalaman buku yang lebih halus yang merupakan kutipan dari teknik ini di: www.20thingsilearned.com. Di sana, Anda akan melihat cara flip halaman dapat diterapkan dalam aplikasi nyata dan betapa andal teknik ini jika dipadukan dengan fitur HTML5 lainnya.
Referensi
- Spesifikasi API Kanvas