Search

יצירת פורטפוליו (Masonry) בוורדפרס עם אנימציית טעינה

כתבתי לא מזמן על Isotope וכיצד להשתמש בספרייה זו בכדי ליצור פילטר או סינון של פוסטים בוורדפרס. כפי שציינתי באותו פוסט -ל Isotope קיים אח צעיר הנקרא masonry.js באמצעותו ניתן ליצור layouts נחמדים ובעצם מאפשר יצירת מתווים מבוססי גריד אך ללא אפשרויות סינון ומיון.

השימוש masonry.js אידיאלי בכדי ליצור פורטפוליו של תמונות,פרוייקטים וכדומה. אז בפוסט זה, אשר לא מעט מהקוד המופיע בו נלקח מהפוסט הבא אותו כתבה Mary Lou, נראה כיצד ליצור פורטפוליו באמצעות masonry.js ונשתמש ב anime.js בכדי ליצור אנימציות טעינה מגניבות לאותו פורטפוליו.

הסיבה שאני כותב פוסט זה הוא בכדי להקל עליכם ליצור פורטפוליו מסוג זה ולחסוך מכם את הצורך לנבור בקוד אותו Mary Lou כתבה. בנוסף, כנראה ובפרוייקט שלכם תבחרו אפקט יחיד בלבד ותממשו את האנימציה בזמן טעינת העמוד ולא בלחיצת כפתור כפי ש Mary מציגה – אראה בהמשך כיצד לעשות זאת.

אציין כי לא ארחיב על הקוד בפוסט אך אראה דוגמאות לאפקטים בהם תוכלו להשתמש, ואסביר כיצד לממש בפועל את אותם אפקטים על ה Masonry (הפורטפוליו שלנו).

בואו נתחיל, אך לא לפני שנראה על מה מדובר ואילו אנימציות ניתן לבצע ע״י הקוד שאצרף בפוסט זה…

אנימציות טעינה לפורטפוליו – דוגמאות לאפקטים

לחצו על הכפתורים מטה בכדי לראות את אפקט הטעינה ואת האופציות העומדות לרשותכם:

אם אתם מעוניינים לדעת כיצד הוסיפתי Lightbox לתמונות בלחיצה תנו מבט בפוסט על הוספת Lightbox לתמונות ותוכן באמצעות Lity.

טעינת הנכסים הרצויים ליצירת הפורטפוליו והאנימציות

הדבר הראשון שנבצע הוא לטעון את הנכסים בתבנית הוורדפרס שלכם. הוסיפו את הקוד הבא לקובץ functions.php בתבנית הבת שלכם. שימו לב כי קיימת תלות בין הקבצים:

function masonry_assets() {

    wp_enqueue_script(
        'masonry',
        get_stylesheet_directory_uri() . '/js/masonry.pkgd.min.js',
        array('jquery'),
        '1.0.1',
        true
    );

    wp_enqueue_script(
        'images-loaded',
        get_stylesheet_directory_uri() . '/js/imagesloaded.pkgd.min.js',
        array('masonry'),
        '1.0.1',
        true
    );

    wp_enqueue_script(
        'anime',
        get_stylesheet_directory_uri() . '/js/anime.min.js',
        array('images-loaded'),
        '1.0.1',
        true
    );

    wp_enqueue_script(
        'masonry-init',
        get_stylesheet_directory_uri() . '/js/masonry-init.js',
        array('anime'),
        '1.0.1',
        true
    );

}

add_action('wp_enqueue_scripts', 'masonry_assets');

את ארבעת הקבצים ניתן למצוא בקישור הבא, העתיקו אלו לתיקיית js בתבנית הבת שלכם. בכדי להקל עליכם הנה התמונות בהן השתמשנו ואלו באדיבות unsplash.com.

ה Markup של הפורטפוליו

ה HTML של הפורטפוליו שלנו נראה כך:

<div class="grid grid--type-c">
	<div class="grid__sizer"></div>
	<div class="grid__item">
		<a class="grid__link" href="#">
			<img class="grid__img no-lazy" src="/img/1.jpg"/>
		</a>
	</div>
	<div class="grid__item">
		<a class="grid__link" href="#">
			<img class="grid__img no-lazy" src="/img/2.jpg"/>
		</a>
	</div>
	<div class="grid__item">
		<a class="grid__link" href="#">
			<img class="grid__img no-lazy" src="/img/3.jpg"/>
		</a>
	</div>
	<div class="grid__item">
		<a class="grid__link" href="#">
			<img class="grid__img no-lazy" src="/img/4.jpg"/>
		</a>
	</div>
	<div class="grid__item">
		<a class="grid__link" href="#">
			<img class="grid__img no-lazy" src="/img/5.jpg"/>
		</a>
	</div>
	<div class="grid__item">
		<a class="grid__link" href="#">
			<img class="grid__img no-lazy" src="/img/6.jpg"/>
		</a>
	</div>
	<div class="grid__item">
		<a class="grid__link" href="#">
			<img class="grid__img no-lazy" src="/img/10.jpg"/>
		</a>
	</div>
	<div class="grid__item">
		<a class="grid__link" href="#">
			<img class="grid__img no-lazy" src="/img/11.jpg"/>
		</a>
	</div>
	<div class="grid__item">
		<a class="grid__link" href="#">
			<img class="grid__img no-lazy" src="/img/7.jpg"/>
		</a>
	</div>
	<div class="grid__item">
		<a class="grid__link" href="#">
			<img class="grid__img no-lazy" src="/img/8.jpg"/>
		</a>
	</div>
</div>

שימו לב כי הוספתי את הקלאס no-lazy להתמונות. קבעתי בתוסף הקאש בו אני משתמש כי תמונות עם קלאס זה לא ייטענו בטעינה עצלה (Lazy Load) מכיוון וזו הפריעה להצגת הפורטפוליו בצורה ראויה.

מיותר לציין כי עליכם להגדיר את הנתיב הנכון לתמונות בהתאם למיקום בו הוספתם אותן.

קובץ העיצוב (CSS) של הפורטפוליו שלנו

הנה ה CSS בו השתמשתי – לא ארחיב על זה אך ייתכן ותאלצו לשחק עימו מעט בכדי להגיע לתוצאה הרצויה וזאת בהתאם ל CSS הקיים בתבנית שלכם:

.loading::before,
.loading::after {
    content: '';
    position: fixed;
    z-index: 1000;
}

.loading::before {
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: #2c2d31;
}

.loading::after {
    top: 50%;
    left: 50%;
    width: 40px;
    height: 40px;
    margin: -20px 0 0 -20px;
    border: 8px solid #383a41;
    border-bottom-color: #565963;
    border-radius: 50%;
    animation: animLoader 0.8s linear infinite forwards;
}


.control.control--effects {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    margin-bottom: 20px;
}

/* Grid */

.grid {
    position: relative;
    z-index: 2;
    display: block;
    margin: 0 auto;
    overflow: hidden;
    opacity: 0;
}

.grid a {
    -webkit-transition: none;
    transition: none;
}

.grid--hidden {
    position: fixed !important;
    z-index: 1;
    top: 0;
    left: 0;
    width: 100%;
    pointer-events: none;
    opacity: 0;
}

.grid--loading::before,
.grid--loading::after {
    content: '';
    z-index: 1000;
}

.grid--loading::before {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(255, 255, 255, 0.9);
}

.grid--loading::after {
    position: absolute;
    top: calc(25vh - -30px);
    left: 50%;
    width: 40px;
    height: 40px;
    margin: 0 0 0 -20px;
    border: 8px solid #383a41;
    border-bottom-color: #565963;
    border-radius: 50%;
    animation: animLoader 0.8s linear forwards infinite;
}

.grid__sizer {
    margin-bottom: 0 !important;
}

.grid__link,
.grid__img {
    display: block;
}

.grid__img {
    width: 100%;
    max-width: unset;
    height: unset;
}

.grid__deco {
    position: absolute;
    top: 0;
    left: 0;
    pointer-events: none;
}

.grid__deco path {
    fill: none;
    stroke: #fff;
    stroke-width: 2px;
}

.grid__reveal {
    position: absolute;
    z-index: 50;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    opacity: 0;
    background-color: #2c2d31;
}

.grid .grid__item,
.grid .grid__sizer {
    width: calc(50% - 20px);
    margin: 0 10px 20px;
}

.grid.grid--type-c.active {
    opacity: 1;
}

@keyframes animLoader {
    to {
        transform: rotate(360deg);
    }
}


@keyframes octocat-wave {
    0%,
    100% {
        transform: rotate(0);
    }
    20%,
    60% {
        transform: rotate(-25deg);
    }
    40%,
    80% {
        transform: rotate(10deg);
    }
}


@media screen and (min-width: 70em) {


    .grid--type-c .grid__item,
    .grid--type-c .grid__sizer {
        width: calc(25% - 16px);
        margin: 0 8px 16px;
    }
}

שימו לב כי עליכם לטעון CSS זה לאחר ה CSS הנטען בתבנית שלכם.

הקובץ masonry-init.js ומימוש אפקט הטעינה על הגריד

בקובץ masonry-init.js אותו צירפתי קודם לכן קיים הקוד המלא האחראי על כל אחד מהאפקטים כמו גם גם אפשרות המימוש של האפקט בלחיצה על כפתור.

סביר להניח כי תרצו לבחור אפקט אחד בלבד בפרוייקט שלכם ולגרום לאפקט זה להתממש בטעינת העמוד ולא בלחיצת כפתור. אז הנה הקוד שמבצע זאת עבור האפקט Hapi:

/**
 * main.js
 * http://www.codrops.com
 *
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Copyright 2017, Codrops
 * http://www.codrops.com
 */
;(function (window) {

    /**
     * GridLoaderFx obj.
     */
    function GridLoaderFx(el, options) {
        this.el = el;
        this.items = this.el.querySelectorAll('.grid__item > .grid__link');
    }

    /**
     * Effects.
     */
    GridLoaderFx.prototype.effects = {
        'Hapi': {
            animeOpts: {
                duration: function (t, i) {
                    return 600 + i * 75;
                },
                easing: 'easeOutExpo',
                delay: function (t, i) {
                    return i * 50;
                },
                opacity: {
                    value: [0, 1],
                    easing: 'linear'
                },
                scale: [0, 1]
            }
        }
    };

    GridLoaderFx.prototype._render = function (effect) {
        // Reset styles.
        this._resetStyles();

        var self = this,
            effectSettings = this.effects[effect],
            animeOpts = effectSettings.animeOpts

        if (effectSettings.perspective != undefined) {
            [].slice.call(this.items).forEach(function (item) {
                item.parentNode.style.WebkitPerspective = item.parentNode.style.perspective = effectSettings.perspective + 'px';
            });
        }

        if (effectSettings.origin != undefined) {
            [].slice.call(this.items).forEach(function (item) {
                item.style.WebkitTransformOrigin = item.style.transformOrigin = effectSettings.origin;
            });
        }

        if (effectSettings.lineDrawing != undefined) {
            [].slice.call(this.items).forEach(function (item) {
                // Create SVG.
                var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
                    path = document.createElementNS('http://www.w3.org/2000/svg', 'path'),
                    itemW = item.offsetWidth,
                    itemH = item.offsetHeight;

                svg.setAttribute('width', itemW + 'px');
                svg.setAttribute('height', itemH + 'px');
                svg.setAttribute('viewBox', '0 0 ' + itemW + ' ' + itemH);
                svg.setAttribute('class', 'grid__deco');
                path.setAttribute('d', 'M0,0 l' + itemW + ',0 0,' + itemH + ' -' + itemW + ',0 0,-' + itemH);
                path.setAttribute('stroke-dashoffset', anime.setDashoffset(path));
                svg.appendChild(path);
                item.parentNode.appendChild(svg);
            });

            var animeLineDrawingOpts = effectSettings.animeLineDrawingOpts;
            animeLineDrawingOpts.targets = this.el.querySelectorAll('.grid__deco > path');
            anime.remove(animeLineDrawingOpts.targets);
            anime(animeLineDrawingOpts);
        }

        if (effectSettings.revealer != undefined) {
            [].slice.call(this.items).forEach(function (item) {
                var revealer = document.createElement('div');
                revealer.className = 'grid__reveal';
                if (effectSettings.revealerOrigin != undefined) {
                    revealer.style.transformOrigin = effectSettings.revealerOrigin;
                }
                if (effectSettings.revealerColor != undefined) {
                    revealer.style.backgroundColor = effectSettings.revealerColor;
                }
                item.parentNode.appendChild(revealer);
            });

            var animeRevealerOpts = effectSettings.animeRevealerOpts;
            animeRevealerOpts.targets = this.el.querySelectorAll('.grid__reveal');
            animeRevealerOpts.begin = function (obj) {
                for (var i = 0, len = obj.animatables.length; i < len; ++i) {
                    obj.animatables[i].target.style.opacity = 1;
                }
            };
            anime.remove(animeRevealerOpts.targets);
            anime(animeRevealerOpts);
        }

        if (effectSettings.itemOverflowHidden) {
            [].slice.call(this.items).forEach(function (item) {
                item.parentNode.style.overflow = 'hidden';
            });
        }

        animeOpts.targets = effectSettings.sortTargetsFn && typeof effectSettings.sortTargetsFn === 'function' ? [].slice.call(this.items).sort(effectSettings.sortTargetsFn) : this.items;
        anime.remove(animeOpts.targets);
        anime(animeOpts);
    };

    GridLoaderFx.prototype._resetStyles = function () {
        this.el.style.WebkitPerspective = this.el.style.perspective = 'none';
        [].slice.call(this.items).forEach(function (item) {
            var gItem = item.parentNode;
            item.style.opacity = 0;
            item.style.WebkitTransformOrigin = item.style.transformOrigin = '50% 50%';
            item.style.transform = 'none';

            var svg = item.parentNode.querySelector('svg.grid__deco');
            if (svg) {
                gItem.removeChild(svg);
            }

            var revealer = item.parentNode.querySelector('.grid__reveal');
            if (revealer) {
                gItem.removeChild(revealer);
            }

            gItem.style.overflow = '';
        });
    };

    window.GridLoaderFx = GridLoaderFx;

    var body = document.body,
        grids = [].slice.call(document.querySelectorAll('.grid')), masonry = [],
        currentGrid = 0,
        // The GridLoaderFx instances.
        loaders = [],
        loadingTimeout;


    function init() {
        // Preload images
        imagesLoaded(body, function () {

            // Initialize Masonry on each grid.
            grids.forEach(function (grid) {
                var m = new Masonry(grid, {
                    itemSelector: '.grid__item',
                    columnWidth: '.grid__sizer',
                    percentPosition: true,
                    transitionDuration: 0,
                    originLeft: false
                });
                masonry.push(m);
                // Hide the grid.
                // Init GridLoaderFx.
                loaders.push(new GridLoaderFx(grid));
            });

            // Remove loading class from body
            body.classList.remove('loading');

            $('.grid').addClass('active');

            loaders[currentGrid]._render('Hapi');
        });
    }

    init();

})(window);

Hapi הוא האפקט היחיד הקיים בקוד זה אך כמובן שניתן להחליפו בכל אפקט אחר על ידי החלפת שורות 25-40 באפקט הרצוי מהקובץ אותו צירפתי קודם לכן.

כמו כן, בקוד זה הסרתי את אפשרות שינוי האפקט בלחיצה על אותם כפתורים ודאגתי לממש את האפקט בטעינת העמוד (שורה 177). אם החלפתם את האפקט באחר יש להחליף גם את שמו בשורה זו בהתאם.

דבר אחרון הוא הוספת תמיכה באתרים מימין לשמאל (RTL) וזאת על ידי הפרמטר originLeft: false בשורה מספר 165.

עד כאן, מקווה שהפוסט עזר לכם. שאלות ותגובות יתקבלו בברכה… 🙂

רועי יוסף
רועי יוסף

מפתח אתרים ותבניות וורדפרס יעודייות על בסיס עיצוב. אוהב טיפוגרפיה, צבעים ומה שבינהם ומכוון לספק אתרים רספונסיבים עם ביצועים גבוהים, מותאמים למנועי חיפוש ובעלי קוד ולידי, סמנטי ונקי.

0 תגובות...

תגובה חדשה

ניווט מהיר

Up!
לבלוג