במדריך זה נראה כיצד ליצור 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) נמצא כאן. אם ישנן שאלות או לא ברור לכם משהו אתם מוזמנים כמובן לשאול בתגובות…