केस स्टडी - 20thingsilearned.com का पेज फ़्लिप इफ़ेक्ट

परिचय

साल 2010 में, F-i.com और Google Chrome टीम ने मिलकर, एचटीएमएल5 पर आधारित एक शिक्षा से जुड़े वेब ऐप्लिकेशन पर काम किया. इसका नाम था, 'ब्राउज़र और वेब के बारे में मुझे जो 20 बातें पता चलीं' (www.20thingsilearned.com). इस प्रोजेक्ट के पीछे एक अहम आइडिया यह था कि इसे किताब के संदर्भ में सबसे बेहतर तरीके से दिखाया जा सकता है. इस किताब में ओपन वेब टेक्नोलॉजी के बारे में काफ़ी जानकारी दी गई है. इसलिए, हमें लगा कि इस किताब के कंटेनर को भी इस तरह से डिज़ाइन करना चाहिए कि इससे पता चल सके कि इन टेक्नोलॉजी की मदद से आज क्या-क्या किया जा सकता है.

'ब्राउज़र और वेब के बारे में मुझे जो 20 बातें पता चलीं' किताब का कवर और होम पेज
"ब्राउज़र और वेब के बारे में मुझे जो 20 बातें पता चलीं" (www.20thingsilearned.com) की किताब का कवर और होम पेज

हमने तय किया कि रीयल वर्ल्ड वाली किताब का अनुभव पाने का सबसे अच्छा तरीका, ऐनालॉग तरीके से पढ़ने के अनुभव के अच्छे हिस्सों को सिम्युलेट करना है. साथ ही, नेविगेशन जैसे क्षेत्रों में डिजिटल दुनिया के फ़ायदों का फ़ायदा लेना है. किताब पढ़ने के फ़्लो को ग्राफ़िक और इंटरैक्टिव बनाने में काफ़ी मेहनत की गई है. खास तौर पर, किताब के पेजों को एक से दूसरे पेज पर फ़्लिप करने के तरीके पर.

शुरू करें

इस ट्यूटोरियल में, कैनवस एलिमेंट और कई JavaScript का इस्तेमाल करके, पेज फ़्लिप इफ़ेक्ट बनाने का तरीका बताया गया है. इस लेख में, वैरिएबल के एलान और इवेंट लिसनर की सदस्यता जैसे कुछ बुनियादी कोड को स्निपेट से बाहर रखा गया है. इसलिए, काम करने वाले उदाहरण का रेफ़रंस रखना न भूलें.

शुरू करने से पहले, डेमो देखना अच्छा रहेगा, ताकि आपको पता चल सके कि हम क्या बनाना चाहते हैं.

मार्कअप

यह हमेशा याद रखना ज़रूरी है कि कैनवस पर जो कुछ भी बनाया जाता है उसे सर्च इंजन इंडेक्स नहीं कर सकते. साथ ही, वेबसाइट पर आने वाले लोग उसे नहीं चुन सकते और ब्राउज़र में खोजने पर भी वह नहीं दिखता. इसलिए, जिस कॉन्टेंट पर काम किया जा रहा है उसे सीधे डीओएम में डाला जाता है. इसके बाद, अगर JavaScript उपलब्ध है, तो उसमें बदलाव किया जाता है. इसके लिए, कम से कम मार्कअप की ज़रूरत होती है:

<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>

हमारे पास किताब के लिए एक मुख्य कंटेनर एलिमेंट है. इसमें हमारी किताब के अलग-अलग पेज और canvas एलिमेंट शामिल हैं. इस एलिमेंट पर, हम फ़्लिप करने वाले पेज बनाएंगे. section एलिमेंट के अंदर, कॉन्टेंट के लिए एक div रैपर होता है - हमें इसकी ज़रूरत होती है, ताकि कॉन्टेंट के लेआउट पर असर डाले बिना, पेज की चौड़ाई बदली जा सके. div की चौड़ाई तय होती है और section को ओवरफ़्लो छिपाने के लिए सेट किया जाता है. इस वजह से, section की चौड़ाई div के लिए हॉरिज़ॉन्टल मास्क के तौर पर काम करती है.

Open Book.
किताब के एलिमेंट में, पेपर टेक्स्चर और ब्राउन रंग की किताब का कवर वाली बैकग्राउंड इमेज जोड़ी गई है.

लॉजिक

पेज फ़्लिप करने के लिए ज़रूरी कोड बहुत मुश्किल नहीं है, लेकिन यह काफ़ी बड़ा है. इसकी वजह यह है कि इसमें प्रोसेस के हिसाब से जनरेट किए गए कई ग्राफ़िक शामिल होते हैं. आइए, सबसे पहले उन स्थिर वैल्यू के ब्यौरे को देखें जिनका इस्तेमाल हम पूरे कोड में करेंगे.

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 को कैनवस के चारों ओर जोड़ा जाता है, ताकि पेज को पलटने पर वह किताब के बाहर तक दिखे. ध्यान दें कि यहां बताई गई कुछ स्थिर वैल्यू, सीएसएस में भी सेट की गई हैं. इसलिए, अगर आपको किताब का साइज़ बदलना है, तो आपको वहां भी वैल्यू अपडेट करनी होंगी.

कॉन्स्टेंट.
इंटरैक्शन को ट्रैक करने और पेज फ़्लिप करने के लिए, पूरे कोड में इस्तेमाल की जाने वाली कॉन्स्टेंट वैल्यू.

इसके बाद, हमें हर पेज के लिए एक फ़्लिप ऑब्जेक्ट तय करना होगा. ये लगातार अपडेट होते रहेंगे, ताकि फ़्लिप की मौजूदा स्थिति को दिखाया जा सके.

// 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
});
}

सबसे पहले, हमें यह पक्का करना होगा कि पेजों को सही तरीके से लेयर किया गया हो. इसके लिए, सेक्शन एलिमेंट के z-index को व्यवस्थित करें, ताकि पहला पेज सबसे ऊपर और आखिरी पेज सबसे नीचे हो. फ़्लिप ऑब्जेक्ट की सबसे ज़रूरी प्रॉपर्टी, progress और target वैल्यू होती हैं. इनका इस्तेमाल यह तय करने के लिए किया जाता है कि पेज को फ़िलहाल कितनी दूर तक folded किया जाना चाहिए. -1 का मतलब है कि पेज को पूरी तरह से बाईं ओर, 0 का मतलब है कि पेज को किताब के बीच में, और +1 का मतलब है कि पेज को किताब के सबसे दाएं किनारे पर फ़ोल्ड किया जाना चाहिए.

प्रोग्रेस.
फ़्लिप की प्रोग्रेस और टारगेट वैल्यू का इस्तेमाल, यह तय करने के लिए किया जाता है कि -1 से +1 स्केल पर, फ़ोल्डिंग पेज को कहां ड्रॉ किया जाना चाहिए.

अब हमारे पास हर पेज के लिए एक फ़्लिप ऑब्जेक्ट है. इसलिए, हमें फ़्लिप की स्थिति अपडेट करने के लिए, उपयोगकर्ता के इनपुट को कैप्चर करना और उसका इस्तेमाल करना शुरू करना होगा.

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 &amp;&amp; page - 1 >= 0) {
    // We are on the left side, drag the previous page
    flips[page - 1].dragging = true;
}
else if (mouse.x > 0 &amp;&amp; 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;
}
}

mouseMoveHandler फ़ंक्शन, mouse ऑब्जेक्ट को अपडेट करता है, ताकि हम हमेशा कर्सर की सबसे हाल की जगह पर काम कर सकें.

mouseDownHandler में, हम यह देखकर शुरू करते हैं कि माउस को बाईं या दाईं ओर के पेज पर दबाया गया था या नहीं, ताकि हमें पता चल सके कि हमें किस दिशा में फ़्लिप करना है. हम यह भी पक्का करते हैं कि उस दिशा में कोई दूसरा पेज मौजूद हो, क्योंकि हो सकता है कि हम पहले या आखिरी पेज पर हों. अगर इन जांचों के बाद, फ़्लिप करने का कोई मान्य विकल्प उपलब्ध होता है, तो हम उससे जुड़े फ़्लिप ऑब्जेक्ट के dragging फ़्लैग को true पर सेट कर देते हैं.

mouseUpHandler पर पहुंचने के बाद, हम सभी flips को देखते हैं और यह जांच करते हैं कि उनमें से किसी को dragging के तौर पर फ़्लैग किया गया है या नहीं. साथ ही, यह भी देखते हैं कि अब उसे रिलीज़ किया जाना चाहिए या नहीं. फ़्लिप रिलीज़ होने पर, हम उसकी टारगेट वैल्यू को उस तरफ़ सेट करते हैं जिस तरफ़ उसे फ़्लिप करना है. यह वैल्यू, माउस की मौजूदा स्थिति के आधार पर तय की जाती है. इस नेविगेशन को दिखाने के लिए, पेज नंबर भी अपडेट किया जाता है.

रेंडरिंग

अब जब हमारा ज़्यादातर लॉजिक तैयार हो गया है, तो हम कैनवस एलिमेंट पर, फ़ोल्डिंग पेपर को रेंडर करने का तरीका जानेंगे. ज़्यादातर काम, render() फ़ंक्शन में होता है. सभी चालू फ़्लिप की मौजूदा स्थिति को अपडेट करने और दिखाने के लिए, इसे हर सेकंड 60 बार कॉल किया जाता है.

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 );
}

}
}

flips को रेंडर करने से पहले, हम clearRect(x,y,w,h) तरीके का इस्तेमाल करके कैनवस को रीसेट करते हैं. पूरे कैनवस को मिटाने पर, परफ़ॉर्मेंस पर काफ़ी असर पड़ता है. इसलिए, सिर्फ़ उन हिस्सों को मिटाना ज़्यादा बेहतर होगा जिन पर ड्रॉ किया जा रहा है. इस ट्यूटोरियल को विषय से जुड़ा रखने के लिए, हम पूरे कैनवस को मिटाने पर ही फ़ोकस करेंगे.

अगर किसी फ़्लिप को खींचा जा रहा है, तो हम उसकी target वैल्यू को अपडेट करते हैं, ताकि वह माउस की पोज़िशन से मेल खा सके. हालांकि, हम इसे असल पिक्सल के बजाय -1 से 1 के स्केल पर अपडेट करते हैं. हम progress को target से दूरी के कुछ हिस्से तक भी बढ़ाते हैं. इससे फ़्लिप आसानी से और ऐनिमेशन के साथ होगा, क्योंकि यह हर फ़्रेम पर अपडेट होता है.

हम हर फ़्रेम पर सभी flips की जांच कर रहे हैं. इसलिए, हमें यह पक्का करना होगा कि हम सिर्फ़ उन flips को फिर से ड्रॉ करें जो चालू हैं. अगर कोई फ़्लिप, किताब के किनारे (BOOK_WIDTH के 0.3% के अंदर) के बहुत करीब नहीं है या उसे dragging के तौर पर फ़्लैग किया गया है, तो उसे ऐक्टिव माना जाता है.

अब सभी लॉजिक लागू हो गए हैं. इसलिए, हमें फ़्लिप की मौजूदा स्थिति के आधार पर, उसका ग्राफ़िक प्रतिनिधित्व बनाना होगा. अब 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';

कोड का यह सेक्शन, कई विज़ुअल वैरिएबल का हिसाब लगाकर शुरू होता है. हमें इन वैरिएबल की ज़रूरत, फ़ोल्ड को ज़्यादा असली तरीके से दिखाने के लिए होती है. यहां, progress वैल्यू का बहुत ज़्यादा महत्व होता है, क्योंकि हमें पेज फ़ोल्ड को यहीं दिखाना होता है. पेज के पलटने के इफ़ेक्ट को बेहतर बनाने के लिए, हम पेपर को किताब के ऊपरी और निचले हिस्से से बाहर तक बढ़ा देते हैं. यह इफ़ेक्ट तब सबसे ज़्यादा असरदार होता है, जब पेज को किताब की स्पाइन के करीब पलटा जाता है.

सेटिंग बदलें
पेज को फ़्लिप करने या खींचने पर, पेज फ़ोल्ड ऐसा दिखता है.

अब सभी वैल्यू तैयार हैं, बस पेपर बनाना बाकी है!

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 &amp; 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();

कैनवस एपीआई के translate(x,y) तरीके का इस्तेमाल, कोऑर्डिनेट सिस्टम को ऑफ़सेट करने के लिए किया जाता है, ताकि हम अपना पेज फ़्लिप 0,0 पोज़िशन के तौर पर काम करने वाली स्पाइन के ऊपर से ड्रॉ कर सकें. ध्यान दें कि ड्रॉ करने के बाद, हमें कैनवस के मौजूदा ट्रांसफ़ॉर्मेशन मैट्रिक को save() और restore() भी करना होगा.

अनुवाद करें
यह वह पॉइंट है जहां से हम पेज फ़्लिप करते हैं. मूल 0,0 पॉइंट, इमेज के सबसे ऊपर बाईं ओर होता है. हालांकि, translate(x,y) की मदद से उस पॉइंट को बदलकर, हम ड्रॉइंग के लॉजिक को आसान बनाते हैं.

foldGradient का इस्तेमाल करके, हम को के आकार में भरेंगे, ताकि उसे असली हाइलाइट और परछाई दी जा सके. हम पेपर ड्रॉइंग के चारों ओर एक बहुत पतली लाइन भी जोड़ते हैं, ताकि हल्के रंग के बैकग्राउंड के साथ इस्तेमाल करने पर, पेपर न दिखे.

अब सिर्फ़ ऊपर बताई गई प्रॉपर्टी का इस्तेमाल करके, folded paper का आकार बनाना बाकी है. हमारे पेपर के बाएं और दाएं हिस्से को सीधी रेखाओं के तौर पर खींचा गया है. साथ ही, ऊपर और नीचे के हिस्सों को घुमाया गया है, ताकि फ़ोल्ड किए गए पेपर की भावना को दिखाया जा सके. इस पेपर के बेंड की स्ट्रेंथ, verticalOutdent वैल्यू से तय होती है.

हो गया! अब आपके पास पेज फ़्लिप नेविगेशन की पूरी सुविधा है.

पेज फ़्लिप करने की सुविधा का डेमो

पेज फ़्लिप इफ़ेक्ट का मकसद, सही इंटरैक्टिव अनुभव देना है. इसलिए, इसकी इमेज देखकर इसका सही अनुभव नहीं मिलता.

अगले चरण

हार्ड-फ़्लिप
इस ट्यूटोरियल में, पेज को धीरे से पलटने की सुविधा को किताब जैसी अन्य सुविधाओं के साथ जोड़ने पर, यह और भी बेहतर हो जाती है. जैसे, इंटरैक्टिव हार्ड कवर.

कैनवस एलिमेंट जैसी HTML5 सुविधाओं का इस्तेमाल करके, क्या-क्या किया जा सकता है, इसका यह सिर्फ़ एक उदाहरण है. हमारा सुझाव है कि आप www.20thingsilearned.com पर जाकर, बेहतर तरीके से बनाई गई किताब को देखें. इस किताब में, पेज पलटने की इस तकनीक के बारे में बताया गया है. यहां आपको यह भी पता चलेगा कि किसी असल ऐप्लिकेशन में पेज पलटने की सुविधा को कैसे लागू किया जा सकता है. साथ ही, यह भी पता चलेगा कि HTML5 की अन्य सुविधाओं के साथ जोड़ने पर, यह सुविधा कितनी बेहतर हो जाती है.

रेफ़रंस

  • Canvas एपीआई की खास बातें