Search

רעיונות מגניבים ליצירת Tooltips באמצעות anime.js

במדריך זה נראה כיצד ליצור Tooltips נחמדים. אותם Tooltips הם בעצם SVG's, כאשר האנימציות עצמן מבוצעות עם Anime.js.

כבר נכתב פוסט על השימוש ב Anime.js ואני ממליץ לקרוא אותו לפני שאתם מנסים להטמיע את ה Tooltips שאנחנו מתארים בפוסט זה. מעבר לכך, שימו לב כי בפוסט זה ישנו שימוש בתכונות CSS מודרניות כגון CSS Grid ו CSS Variables, לכן יש לצפות בדפדפנים מודרניים בלבד.

כל הדוגמאות ומרבית הקוד למעט שינויים מינוריים נלקחו מ Codrops ונוצרו על ידי Marry Lou.

בואו לא נשכח את החיות. ומה לגבי היערות?

בואו לא נשכח את החיות. ומה לגבי היערות?

בואו לא נשכח את החיות. ומה לגבי היערות?

בואו לא נשכח את החיות. ומה לגבי היערות?

בואו לא נשכח את החיות. ומה לגבי היערות?

בואו לא נשכח את החיות. ומה לגבי היערות?
בואו לא נשכח את החיות. ומה לגבי היערות העצים והעשבים השוטים?

בואו לא נשכח את החיות. ומה לגבי היערות?

בואו לא נשכח את החיות. ומה לגבי היערות?

בואו לא נשכח את החיות. ומה לגבי היערות?

בואו לא נשכח את החיות. ומה לגבי היערות?

בואו לא נשכח את החיות. ומה לגבי היערות?

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

כך נראה האובייקט המכיל את הגדרות האנימציה עבור הדוגמה הראשונה:

const config = {
    cora: {
        in: {
            base: {
                duration: 600,
                easing: 'easeOutQuint',
                scale: [0, 1],
                rotate: [-180, 0],
                opacity: {
                    value: 1,
                    easing: 'linear',
                    duration: 100
                }
            },
            content: {
                duration: 300,
                delay: 250,
                easing: 'easeOutQuint',
                translateY: [20, 0],
                opacity: {
                    value: 1,
                    easing: 'linear',
                    duration: 100
                }
            },
            trigger: {
                duration: 300,
                easing: 'easeOutExpo',
                scale: [1, 0.9],
                color: '#6fbb95'
            }
        },
        out: {
            base: {
                duration: 150,
                delay: 50,
                easing: 'easeInQuad',
                scale: 0,
                opacity: {
                    delay: 100,
                    value: 0,
                    easing: 'linear'
                }
            },
            content: {
                duration: 100,
                easing: 'easeInQuad',
                translateY: 20,
                opacity: {
                    value: 0,
                    easing: 'linear'
                }
            },
            trigger: {
                duration: 300,
                delay: 50,
                easing: 'easeOutExpo',
                scale: 1,
                color: '#666'
            }
        }
    }
};

וזהו ה Javascript שאחראי להפעיל את אותה אנימציה באמצעות Anime.js. קוד זה רלוונטי לכל אחת מהדוגמאות מעלה:


const tooltips = Array.from(document.querySelectorAll('.tooltip'));

class Tooltip {
    constructor(el) {
        this.DOM = {};
        this.DOM.el = el;
        this.type = this.DOM.el.getAttribute('data-type');
        this.DOM.trigger = this.DOM.el.querySelector('.tooltip__trigger');
        this.DOM.triggerSpan = this.DOM.el.querySelector('.tooltip__trigger-text');
        this.DOM.base = this.DOM.el.querySelector('.tooltip__base');
        this.DOM.shape = this.DOM.base.querySelector('.tooltip__shape');
        if (this.DOM.shape) {
            this.DOM.path = this.DOM.shape.childElementCount > 1 ? Array.from(this.DOM.shape.querySelectorAll('path')) : this.DOM.shape.querySelector('path');
        }
        this.DOM.deco = this.DOM.base.querySelector('.tooltip__deco');
        this.DOM.content = this.DOM.base.querySelector('.tooltip__content');

        this.DOM.letters = this.DOM.content.querySelector('.tooltip__letters');
        if (this.DOM.letters) {
            // Create spans for each letter.
            charming(this.DOM.letters);
            // Redefine content.
            this.DOM.content = this.DOM.letters.querySelectorAll('span');
        }
        this.initEvents();
    }

    initEvents() {
        this.mouseenterFn = () => {
            this.mouseTimeout = setTimeout(() => {
                this.isShown = true;
                this.show();
            }, 75);
        }
        this.mouseleaveFn = () => {
            clearTimeout(this.mouseTimeout);
            if (this.isShown) {
                this.isShown = false;
                this.hide();
            }
        }
        this.DOM.trigger.addEventListener('mouseenter', this.mouseenterFn);
        this.DOM.trigger.addEventListener('mouseleave', this.mouseleaveFn);
        this.DOM.trigger.addEventListener('touchstart', this.mouseenterFn);
        this.DOM.trigger.addEventListener('touchend', this.mouseleaveFn);
    }

    show() {
        this.animate('in');
    }

    hide() {
        this.animate('out');
    }

    animate(dir) {
        if (config[this.type][dir].base) {
            anime.remove(this.DOM.base);
            let baseAnimOpts = {targets: this.DOM.base};
            anime(Object.assign(baseAnimOpts, config[this.type][dir].base));
        }
        if (config[this.type][dir].shape) {
            anime.remove(this.DOM.shape);
            let shapeAnimOpts = {targets: this.DOM.shape};
            anime(Object.assign(shapeAnimOpts, config[this.type][dir].shape));
        }
        if (config[this.type][dir].path) {
            anime.remove(this.DOM.path);
            let shapeAnimOpts = {targets: this.DOM.path};
            anime(Object.assign(shapeAnimOpts, config[this.type][dir].path));
        }
        if (config[this.type][dir].content) {
            anime.remove(this.DOM.content);
            let contentAnimOpts = {targets: this.DOM.content};
            anime(Object.assign(contentAnimOpts, config[this.type][dir].content));
        }
        if (config[this.type][dir].trigger) {
            anime.remove(this.DOM.triggerSpan);
            let triggerAnimOpts = {targets: this.DOM.triggerSpan};
            anime(Object.assign(triggerAnimOpts, config[this.type][dir].trigger));
        }
        if (config[this.type][dir].deco) {
            anime.remove(this.DOM.deco);
            let decoAnimOpts = {targets: this.DOM.deco};
            anime(Object.assign(decoAnimOpts, config[this.type][dir].deco));
        }
    }

    destroy() {
        this.DOM.trigger.removeEventListener('mouseenter', this.mouseenterFn);
        this.DOM.trigger.removeEventListener('mouseleave', this.mouseleaveFn);
        this.DOM.trigger.removeEventListener('touchstart', this.mouseenterFn);
        this.DOM.trigger.removeEventListener('touchend', this.mouseleaveFn);
    }
}

const init = (() => tooltips.forEach(t => new Tooltip(t)))();

הוסיפו שני קטעי קוד אלו לקובץ Javascript ותדאגו לטעון אותם לאחר שאתם טוענים את Anime.js כמובן. ה HTML של הדוגמה הראשונה נראה כך:

<div class="grid__item theme-1">
    <div class="tooltip tooltip--cora" data-type="cora">
        <div class="tooltip__trigger" role="tooltip" aria-describedby="info-cora"><span
                class="tooltip__trigger-text">Cora</span></div>
        <div class="tooltip__base">
            <svg class="tooltip__shape" width="100%" height="100%" viewbox="0 0 400 300">
                <path d="M 199,21.9 C 152,22.2 109,35.7 78.8,57.4 48,79.1 29,109 29,142 29,172 45.9,201 73.6,222 101,243 140,258 183,260 189,270 200,282 200,282 200,282 211,270 217,260 261,258 299,243 327,222 354,201 371,172 371,142 371,109 352,78.7 321,57 290,35.3 247,21.9 199,21.9 Z"></path>
            </svg>
            <div class="tooltip__content" id="info-cora">בואו לא נשכח את החיות. ומה לגבי היערות?</div>
        </div>
    </div>
</div>

שימו לב – אם לאחר ההטמעה אתם רואים התנהגות מוזרה, נסו לבצע Minify ל HTML זה ובדקו שוב.

ה CSS של הדוגמה הראשונה נראה כך:

.tooltip {
    position: relative;
    display: inline-block;
}

.tooltip__trigger {
    cursor: pointer;
    position: relative;
}

.tooltip__trigger-text {
    display: block;
    padding: 0.85em;
    pointer-events: none;
    color: var(--color-action) !important;
}

.tooltip__base {
    position: absolute;
    bottom: 2em;
    left: 50%;
    margin-left: -150px;
    width: 300px;
    height: 200px;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    pointer-events: none;
}

.tooltip__content {
    color: var(--color-button-text);
    display: flex;
    position: relative;
    align-items: center;
    justify-content: center;
    width: 65%;
    padding: 0 1em;
    opacity: 0;
    font-size: 0.85em;
}

.tooltip__shape,
.tooltip__deco {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
}

.tooltip__shape {
    fill: var(--color-button-bg);
}

/* Individual Styles */

/* Cora */

.theme-1 {
    --color-item-bg: #f06160;
    --color-action: #fefefe;
    --color-button-bg: #cb504e;
    --color-button-text: #fefefe;
    --button-padding: 1.5rem 3rem;
    --radius-button: 5px;
    --border-button: 0;
}

.tooltip--cora .tooltip__base {
    transform-origin: 50% 100%;
}

.tooltip--cora .tooltip__content {
    margin-bottom: 1em;
}

החלק בקוד CSS זה הספציפי לדוגמה הראשונה מופיע לאחר ההערה בקוד Individual Styles.

קוד ה CSS המלא עבור כל הדוגמאות נמצא כאן. קוד ה Javascript המלא (ללא Anime.js) נמצא כאן. אם ישנן שאלות או לא ברור לכם משהו אתם מוזמנים כמובן לשאול בתגובות…

 

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

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

0 תגובות...

תגובה חדשה

Up!
לבלוג