تخطيط صفحات ويب على شكل صفحات كتاب قابلة للتقليب باستخدام CSS


عمر الوريكات

كما هو موضح في العنوان فإنّك في هذا الدرس سوف تتعلم كيفية إنشاء تأثير وكأنّك تتصفح كتابًا ما. وسوف نستخدم في هذا الدرس إضافة تدعى BookBlock، والفكرة من هذا الدرس هو أنّك سوف تقوم بإنشاء تأثير يُمكّن الزوار من تصفح موقع ما وكأنهم يتصفحون أحد الكتب.

book-css.thumb.png.dc549fbf32e99c27b0d05

الفكرة هي أنّك سوف تتصفح صفحات الموقع باستخدام سهمين سوف يكونان موجودين في أعلى الصفحة أو أزرار لوحة المفاتيح أو حتى بالسحب باستخدام الفأرة وأيضًا سيكون هناك قائمة جانبية سوف تظهر عندما تقوم بالضغط على أيقونة ما. وسوف تحتوي القائمة الجانبية على روابط لصفحات الموقع وعندما تقوم بالضغط على أحد هذه الروابط فإنّها سوف تنقلك إلى الصفحة المطلوبة.

سوف نستخدم أيضًا إضافة اسمها jScrollPane وذلك للحصول على شريط تمرير (scrollbar) يظهر عندما يكون المحتوى أطول من ارتفاع المتصفح.

وهذه قائمة بإضافات jQuery التي سوف نستخدمها:

  1. BookBlock
  2. Custom jQuery++
  3. jScrollPane
  4. jQuery Mouse Wheel Plugin
  5. Custom Mdernizer

يمكنك معاينة المثال الموضح في هذا الدرس من هنا.

كما يمكنك تحميل الملفات المصدرية.

بنية ملف HTML

في البداية يجب أن يكون لدينا حاوٍ رئيسي لاحتواء جميع العناصر، وداخل هذا الحاوي سوف يكون هناك عنصر <div> للقائمة الجانبية وسوف نعطيه فئة (class) بالاسم "menu-panel" وسوف يكون هناك عنصر <div> آخر يحتوي على المحتوى الرئيسي للموقع وسوف نعطيه فئة بالاسم "bb-custom-wrapper". وفي داخل كل قسم سوف يكون هناك حاوٍ للمحتوى وعنصر <div> سوف نحتاجه من أجل شريط التمرير الذي ذكرناه سابقًا.

<div id="container" class="container">
    <div class="menu-panel">
        <h3>Table of Contents</h3>
        <ul id="menu-toc" class="menu-toc">
            <li class="menu-toc-current"><a href="#item1">Self-destruction</a></li>
            <li><a href="#item2">Why we die</a></li>
            <li><a href="#item3">The honeymoon</a></li>
            <li><a href="#item4">A drawing joke</a></li>
            <li><a href="#item5">Commencing practice</a></li>
        </ul>
    </div>

    <div class="bb-custom-wrapper">
        <div id="bb-bookblock" class="bb-bookblock">
            <div class="bb-item" id="item1">
                <div class="content">
                    <div class="scroller">
                        <h2>Self-destruction</h2>
                        <p>...</p>
                    </div>
                </div><!-- /content -->
            </div><!-- /bb-item -->

            <div class="bb-item" id="item2"><!-- ... --></div>
            <div class="bb-item" id="item3"><!-- ... --></div>
            <div class="bb-item" id="item4"><!-- ... --></div>
            <div class="bb-item" id="item5"><!-- ... --></div>
        </div><!-- /bb-bookblock -->

        <nav>
            <a id="bb-nav-prev" href="#">←</a>
            <a id="bb-nav-next" href="#">→</a>
        </nav>

        <span id="tblcontents" class="menu-button">Table of Contents</span>
    </div><!-- /bb-custom-wrapper -->
</div><!-- /container -->

سوف نقوم بربط عناصر القائمة الجانبية بصفحات الموقع (التي تحمل الفئة "bb-item)، وسوف نُضيف أيضًا سهمين في أعلى الصفحة من أجل التنقل بين الصفحات وزر يقوم بفتح وإغلاق القائمة الجانبية.

لنقم الآن بإضافة تنسيقات CSS.

تنسيقات CSS

لن نتحدث هنا عن التنسيقات التي تأتي مع إضافة BookBlock لأنك سوف تجدها داخل ملف bookblock.css، وإنّما سوف نُركّز على التنسيقات الأخرى المهمة.

لنبدأ التنسيقات بإضافة سطر يقوم بجلب الخط المسمى "Lato" من خدمة Google web fonts:

@import url(http://fonts.googleapis.com/css?family=Lato:300,400,700);

سوف نقوم بإعطاء الوسم <html> الخاصية height: 100% وذلك لأننا سوف نحتاج أن نجعل بعض العناصر تتمدد على ارتفاع المتصفح كاملًا:

html {
    height: 100%;
}

سوف نستخدم أيضًا الخاصية box-sizing: border-box وذلك حتى نستخدم قيم مئوية لكل من العرض والإرتفاع أثناء استخدام padding دون القلق حول أبعاد العناصر والقيام بعمليات حسابية نحن بغنىً عنها:

*,
*:after,
*:before {
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
    padding: 0;
    margin: 0;
}

لنقم الآن بتعريف الخط الذي سوف نستخدمه في الموقع (العنصر body) ونُعطيه ارتفاع بقيمة 100% (تذكّر أننا نريد ارتفاع بهذه القيمة لأنه سوف يكون هناك عناصر تتموضع بشكل مطلق (absolute positioning) وسوف تحتاج أن تتمدد على ارتفاع المتصفح كاملًا):

body {
    font-family: 'Lato', Calibri, Arial, sans-serif;
    font-weight: 400;
    font-size: 100%;
    color: #333;
    height: 100%;
}

سوف نستعمل أيضًا إضافة Modernizr وسوف نُضيف الفئة "no-js" إلى وسم <html>، فإذا كانت الجافاسكربت مفعّلة فإنّ Modernizr سوف يستبدل تلك الفئة بالفئة "js". وهذا سوف يساعدنا على إعطاء خصائص CSS معينة لبعض العناصر التي لا نريدها إذا كانت الجافاسكربت معطّلة.

لاحظ أيضًا أننا نحتاج أن يكون عرض الصفحة وارتفاعها بقيمة 100% فقط إذا كانت الجافاسكربت مفعّلة وعندها فقط نريد من العنصر body أن يكون له خاصية overflow: hidden:

.js body {
    overflow: hidden;
}

وهذه بعض التنسيقات الخاصة بالروابط:

a {
    color: #555;
    text-decoration: none;
}

a:hover {
    color: #000;
}

نريد من الحاوي الرئيسي أن يكون بعرض وارتفاع المتصفح كاملًا، وسوف نقوم بموضعة القائمة الجانبية خارج هذا الحاوي باستعمال الخاصية left وإعطائها قيمة سالبة تكون بنفس قيمة العرض الخاص بالقائمة الجانبية. والفكرة هي أنّه عند النقر على أيقونة القائمة الجانبية فإنّ الحاوي سوف يتحرك إلى اليمين مما يؤدي إلى ظهور القائمة الجانبية.

دعونا إذًا نقوم بإعطاء الحاوي الرئيسي عرضًا وارتفاعًا بقيمة 100% وأن نُضيف الخاصية transition إلى الحاوي container:

.container,
.bb-custom-wrapper,
.bb-bookblock {
    width: 100%;
    height: 100%;
}

.container {
    position: relative;
    left: 0px;
    transition: left 0.3s ease-in-out;
}

عند النقر على أيقونة القائمة الجانبية فإن فئة (class) أخرى سوف يتم إضافتها إلى الحاوي container والتي سوف تحتوي على الخاصية left: 240px (نفس العرض الخاص بالقائمة الجانبية) وبالتالي فإنّ الصفحة كاملة سوف تتحرك إلى اليمين بمقدار 240px وبالتالي ظهور القائمة الجانبية:

.slideRight {
    left: 240px;
}

ولكن بدون الجافاسكربت لن نكون قادرين على القيام بما سبق لذلك سوف نقوم بإضافة الخاصية padding-left: 240px:

.no-js .container {
    padding-left: 240px;
}

ونريد أن تكون القائمة الجانبية ثابتة في الجانب الأيسر بشكل افتراضي:

.menu-panel {
    background: #f1103a;
    width: 240px;
    height: 100%;
    position: fixed;
    z-index: 1000;
    top: 0;
    left: 0;
    text-shadow: 0 1px 1px rgba(0,0,0,0.1);
}

وإذا كانت الجافاسكربت مفعلة فسوف نقوم بموضعة القائمة الجانبية بشكل مطلق وإلى اليسار بقيمة -240px:

.js .menu-panel {
    position: absolute;
    left: -240px;
}

وهذه هي التنسيقات الخاصة بعناصر القائمة الجانبية:

.menu-panel h3 {
    font-size: 1.8em;
    padding: 20px;
    font-weight: 300;
    color: #fff;
    box-shadow: inset 0 -1px 0 rgba(0,0,0,0.05);
}

.menu-toc {
    list-style: none;
}

.menu-toc li a {
    display: block;
    color: #fff;
    font-size: 1.1em;
    line-height: 3.5;
    padding: 0 20px;
    cursor: pointer;
    background: #f1103a;
    border-bottom: 1px solid #dd1338;
}

.menu-toc li a:hover,
.menu-toc li.menu-toc-current a{
    background: #dd1338;
}

وأمّا بالنسبة للقائمة الرئيسية التي سوف تحتوي على السهمين فإننا سوف نقوم بموضعتها بشكل مطلق وفوق جميع العناصر الأخرى:

.bb-custom-wrapper nav {
    top: 20px;
    left: 60px;
    position: absolute;
    z-index: 1000;
}

كما أنّ روابط السهمين وزر القائمة الجانبية سوف يتموضعان بشكل مطلق (position: absolute) وسوف نعطيها الخاصية border-radius: 50% لنجعلها تظهر كالدائرة:

.bb-custom-wrapper nav span,
.menu-button {
    position: absolute;
    width: 32px;
    height: 32px;
    top: 0;
    left: 0;
    background: #f1103a;
    border-radius: 50%;
    color: #fff;
    line-height: 30px;
    text-align: center;
    speak: none;
    font-weight: bold;
    cursor: pointer;
}

.bb-custom-wrapper nav span:last-child {
    left: 40px;
}

.bb-custom-wrapper nav span:hover,
.menu-button:hover {
    background: #000;
}

سوف يكون الزر الذي يفتح ويغلق القائمة الجانبية موجودًا في أعلى يسار الصفحة وسوف نقوم بإخفاء النص الموجود بداخله (نريد أن تظهر الأيقونة فقط):

.menu-button {
    z-index: 1000;
    left: 20px;
    top: 20px;
    text-indent: -9000px;
}

لنقم الآن بإنشاء أيقونة بسيطة بدون استعمال أي صور وذلك باستعمال العنصر الزائف :after واستعمال الخاصية box-shadow والتي سوف تعمل على إنشاء الخطين العلوي والسفلي للأيقونة:

.menu-button:after {
    position: absolute;
    content: '';
    width: 50%;
    height: 2px;
    background: #fff;
    top: 50%;
    margin-top: -1px;
    left: 25%;
    box-shadow: 0 -4px #fff, 0 4px #fff;
}

وفي حالة كان الجافاسكربت معطلًا فإننا نريد أن نخفي هذه العناصر:

.no-js .bb-custom-wrapper nav span,
.no-js .menu-button {
    display: none;
}

لننتقل الآن إلى تنسيق الأجزاء الداخلية لكل قسم من أقسام الصفحة (bb-item). نريد أن يتم موضعة المحتوى (content) بشكل مطلق ونريد ان نستعمل الخاصية overflow: hidden، وهذا مهم لأننا نريد تطبيق شريط التمرير هنا ونريد أن نفعل ذلك فقط عند قلب/تغيير الصفحة. فإذا لم نستخدم الخاصية overflow: hidden فإنّك سوف ترى المحتوى يتداخل ببعضه. وأعيد وأكرر مرة أخرى بأنّ هذا سوف يحدث فقط إذا كان الجافاسكربت مفعلًا ولذلك سوف نستخدم الفئة "js":

.js .content {
    position: absolute;
    top: 60px;
    left: 0;
    bottom: 50px;
    width: 100%;
    overflow: hidden;
}

العنصر <div class="scroller"> هو الذي سوف ينمو مع المحتوى لذلك سوف نعطيه الخاصية padding:

.scroller {
    padding: 10px 5% 10px 5%;
}

لاحظ أننا استعملنا قيم مئوية للجوانب وذلك حتى نجعل الصفحة تتجاوب مع حجم الشاشة.

دعونا نتخلص من الحواف الحادة عندما نقوم بالتمرير (scroll) وذلك باستخدام العناصر الزائفة إلى أعلى وأسفل عنصر المحتوى مع استخدام تدرج بين اللون الأبيض والشّفّاف:

.js .content:before,
.js .content:after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 20px;
    z-index: 100;
    pointer-events: none;
    background:
        linear-gradient(
            to bottom,
            rgba(255,255,255,1) 0%,
            rgba(255,255,255,0) 100%
        );
}

.js .content:after {
    top: auto;
    bottom: 0;
    background:
        linear-gradient(
            to top,
            rgba(255,255,255,1) 0%,
            rgba(255,255,255,0) 100%
        );
}

هذا سوف يجعل النص يظهر بشكل باهت.

لنقم الآن بتنسيق عناصر النصوص:

.content h2 {
    font-weight: 300;
    font-size: 4em;
    padding: 0 0 10px;
    color: #333;
    margin: 0 1% 40px;
    text-align: left;
    box-shadow: 0 10px 0 rgba(0,0,0,0.02);
    text-shadow: 0 0 2px #fff;
}

.no-js .content h2 {
    padding: 40px 1% 20px;
}

.content p {
    font-size: 1.2em;
    line-height: 1.6;
    font-weight: 300;
    padding: 5px 8%;
    text-align: justify;
}

كل ما تبقى علينا الآن من تنسيقات CSS هو استخدام الـmedia queries. فإذا كانت الجافاسكربت معطلة فإننا لا نريد أن تظهر القائمة الجانبية إذا كان العرض أقل من 800px. كان هذا فقط مثالًا بسيطًا على كيفية التحكم بالعناصر تحت ظروف وشروط معينة.

الـmedia query الأخيرة سوف تعمل على تكبير الخط قليلًا من أجل الأجهزة صغيرة الحجم كالهواتف.

@media screen and (max-width: 800px){
    .no-js .menu-panel {
         display: none;
    }

    .no-js .container {
        padding: 0;
    }
}

@media screen and (max-width: 400px){
    .menu-panel,
    .content {
         font-size: 75%;
    }
}

كان هذا كل ما يتعلق بتنسيقات CSS ويتبقى علينا استخدام بعض الجافاسكربت.

بعض الجافاسكربت

سوف نبدأ اولًا بتخزين (caching) بعض العناصر حتى لا نضطر إلى استدعائها في كل مرة وسوف نقوم أيضًا بتهئية/مناداة إضافة BookBlock. نُريد أيضًا أن نقوم بضبط بعض الأمور بعد كل قلب/تغيير للصفحة وهذه الأمور هي رقم الصفحة الحالية والسلوك الخاص بإضافة jScrollPane. وهذا محدد في الاستدعاء الخلفي (callback) المسمى onEndFlip والممرر إلى إضافة BookBlock.

var $container = $( '#container' ),

// the element we will apply the BookBlock plugin to
$bookBlock = $( '#bb-bookblock' ),

// the BookBlock items (bb-item)
$items = $bookBlock.children(),

// index of the current item
current = 0,

// initialize the BookBlock
bb = $( '#bb-bookblock' ).bookblock( {
    speed : 800,
    perspective : 2000,
    shadowSides : 0.8,
    shadowFlip : 0.4,
    // after each flip...
    onEndFlip : function(old, page, isLimit) {
         // update the current value
         current = page;
         
         // update the selected item of the table of contents (TOC)
         updateTOC();

         // show and/or hide the navigation arrows
         updateNavigation( isLimit );

         // initialize the jScrollPane on the content div for the new item
         setJSP( 'init' );

         // destroy jScrollPane on the content div for the old item
         setJSP( 'destroy', old );
    }
} ),

// the navigation arrows
$navNext = $( '#bb-nav-next' ),
$navPrev = $( '#bb-nav-prev' ).hide(),

// the table of content items
$menuItems = $container.find( 'ul.menu-toc > li' ),

// button to open the TOC
$tblcontents = $( '#tblcontents' ),

transEndEventNames = {
    'WebkitTransition': 'webkitTransitionEnd',
    'MozTransition': 'transitionend',
    'OTransition': 'oTransitionEnd',
    'msTransition': 'MSTransitionEnd',
    'transition': 'transitionend'
},

// transition event name
transEndEventName = transEndEventNames[Modernizr.prefixed('transition')],

// check if transitions are supported
supportTransitions = Modernizr.csstransitions;

لنقم أولًا بربط الأحداث ببعض العناصر التي تم تهيئتها سابقًا، كما أننا نريد أن نقوم بتهيئة jScrollPane لأول عنصر (العنصر الحالي).

function init() {
    // initialize jScrollPane on the content div of the first item
    setJSP( 'init' );
    initEvents();
}

بما أننا سوف نقوم بتهيئة وإعادة تهيئة وتدمير jScrollPane فلنقم بتعريف دالة لذلك:

function setJSP( action, idx ) {
    var idx = idx === undefined ? current : idx,
    $content = $items.eq( idx ).children( 'div.content' ),
    apiJSP = $content.data( 'jsp' );

    if( action === 'init' && apiJSP === undefined ) {
        $content.jScrollPane({verticalGutter : 0, hideFocus : true });
    }
    else if( action === 'reinit' && apiJSP !== undefined ) {
        apiJSP.reinitialise();
    }
    else if( action === 'destroy' && apiJSP !== undefined ) {
        apiJSP.destroy();
    }
}

سوف نحتاج إلى ربط العديد من الأحداث كالتالي:

  1. سوف يتم استدعاء الدالتين ()next و()prev الخاصتين بإضافة BookBlock وذلك عند النقر على أزرار التنقل أو السحب باستخدام الفأرة.
  2. سوف يظهر جدول المحتويات أو يختفي عند النقر على زر القائمة (tblcontents$).
  3. سوف يتم استدعاء الدالة ()jump الخاصة بإضافة BookBlock وذلك عند النقر على أي عنصر من عناصر جدول المحتويات.
  4. سوف يتم تهيئة jScrollPane عند القيام بتغيير حجم النافذة (window resize).
function initEvents() {
    // add navigation events
    $navNext.on( 'click', function() {
        bb.next();
        return false;
    } );

    $navPrev.on( 'click', function() {
        bb.prev();
        return false;
    } );

    // add swipe events
    $items.on( {
        'swipeleft' : function( event ) {
             if( $container.data( 'opened' ) ) {
                 return false;
             }
             bb.next();
             return false;
         },

        'swiperight' : function( event ) {
            if( $container.data( 'opened' ) ) {
                return false;
            }
            bb.prev();
            return false;
        }
    } );

    // show TOC
    $tblcontents.on( 'click', toggleTOC );

    // click a menu item
    $menuItems.on( 'click', function() {
        var $el = $( this ),
        idx = $el.index(),
        jump = function() {
            bb.jump( idx + 1 );
        };
        current !== idx ? closeTOC( jump ) : closeTOC();
        return false;
    } );

    // reinit jScrollPane on window resize
    $( window ).on( 'debouncedresize', function() {
        // reinitialise jScrollPane on the content div
        setJSP( 'reinit' );
    } );
}

ظهور أزرار التنقل من عدمه سوف يعتمد على الصفحة الحالية، فإذا كُنّا في الصفحة الأولى فإننا سوف نرى فقط زر "التالي" وإذا كُنّا في الصفحة الأخيرة فإننا سوف نرى فقط زر "السابق":

function updateNavigation( isLastPage ) {
    if( current === 0 ) {
        $navNext.show();
        $navPrev.hide();
    }
    else if( isLastPage ) {
        $navNext.hide();
        $navPrev.show();
    }
    else {
        $navNext.show();
        $navPrev.show();
    }
}

عندما نفتح جدول المحتويات (القائمة الجانبية) فإننا نريد أن تختفي عناصر التنقل وأن تظهر مرة أخرى عندما نقوم بإغلاق القائمة الجانبية.

سوف نقوم بتحريك القائمة الجانبية باستخدام خاصية transition، وإذا لم تكن هذه الخاصية مدعومة من المتصفح فإننا سوف نستخدم fallback بسيط:

function toggleTOC() {
    var opened = $container.data( 'opened' );
    opened ? closeTOC() : openTOC();
}

function openTOC() {
    $navNext.hide();
    $navPrev.hide();
    $container.addClass( 'slideRight' ).data( 'opened', true );
}

function closeTOC( callback ) {
    $navNext.show();
    $navPrev.show();
    $container.removeClass( 'slideRight' ).data( 'opened', false );
    if( callback ) {
        if( supportTransitions ) {
            $container.on( transEndEventName, function() {
                 $( this ).off( transEndEventName );
                 callback.call();
            } );
        }
        else {
            callback.call();
        }
    }
}

خاتمة

هذا كان كل شيء يخص هذا الدرس أتمنى أن يكون قد أعجبك وأن تكون قد وجدته مفيدًا.

ترجمة -وبتصرّف- للدرس Fullscreen Pageflip Layout لصاحبته Mary Lou.



4 اشخاص أعجبوا بهذا


تفاعل الأعضاء


مقال أكثر من رائع .. تحياتي لك أستاذ عمر

شارك هذا التعليق


رابط هذا التعليق
شارك على الشبكات الإجتماعية

هل موجود جاهز كقالب وورد بريس ؟

شارك هذا التعليق


رابط هذا التعليق
شارك على الشبكات الإجتماعية


يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن