enami
Version:
Animation on scroll package
425 lines (281 loc) • 10.2 kB
JavaScript
/*!
* enami
* Animation on scroll
* 2020 Enzo Vergara
* MIT License
* https://github.com/enzoemb/enami
*/
/**
* Based on cferdinandi's starter template
* https://gist.github.com/cferdinandi/57c96241114fc6e8ce6cd2c5bfeeb346
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], function () {
return factory(root);
});
} else if (typeof exports === 'object') {
module.exports = factory(root);
} else {
root.enami = factory(root);
}
})(typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : this, function (window) {
'use strict';
var window = window.root; // Map window to root to avoid confusion
// Default settings
var defaults = {
// element: null,
offset: '0px 0px 0px 0px',
delay: null,
duration: null,
once: true,
disableOnMobile: false,
threshold: 0,
selector: null,
root: null,
reset: false
};
/**
* Helper functions
*/
// emit events
var emitEvent = function (type, element, detail = {}) {
var event = new CustomEvent(type, detail);
if (element) {
element.dispatchEvent(event);
} else {
document.dispatchEvent(event);
}
};
// detect mobile
var isMobile = function () {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
};
// seconds attribute to ms
var secondsToMs = function (attr) {
var attr = attr.trim();
var number = attr.replace(/[^0-9.]/g, '');
if (attr.endsWith('ms')) { // is 100ms
return parseInt(number);
} else {
return number * 1000;
}
}
// setup default styles
var setupStyles = function () {
var style = document.createElement('style');
style.innerHTML = `
[data-enami] {
opacity: 0.001;
}
`;
document.head.appendChild(style);
}
// execute animation in
var enamiteIn = function (element, settings) {
emitEvent('enami:animate-in', element)
// console.log(settings);
// set delay
let delay = element.getAttribute("data-enami-delay");
if (delay && element.style.transitionDelay == "") { // set data-attribute delay if has delay and if dont have already a delay
// element.style.transitionDelay = delay;
element.style.animationDelay = delay;
} else if (settings.delay != null && element.style.transitionDelay == "") { // set property delay
// element.style.transitionDelay = settings.delay;
element.style.animationDelay = settings.delay;
}
// set duration
let duration = element.getAttribute("data-enami-duration");
if (duration) {
// element.style.transitionDuration = duration;
element.style.animationDuration = duration;
} else if (settings.duration != null) {
// element.style.transitionDuration = settings.duration;
element.style.animationDuration = settings.duration;
}
// add attributes
element.removeAttribute("data-enami-out");
// reflow(element);
element.setAttribute("data-enami-in", "");
}
// execute out animation
var enamiteOut = function (element) {
emitEvent('enami:animate-out', element)
// add attributes
element.removeAttribute("data-enami-in");
// reflow(element);
element.setAttribute("data-enami-out", "");
}
// reset animations
var enamiteReset = function (element) {
// element.style.transition = 'false';
element.style.animation = 'false';
element.removeAttribute('data-enami-in')
// reflow(element);
setTimeout(function () {
// element.style.transition = '';
element.style.animation = '';
}, 1)
}
var reflow = function (element) {
// console.log(element.offsetHeight);
}
// children logic
var enami = function (options) {
var observer, parentEnamis, parentSelector, childEnamis;
var enami = {}; // Object for public APIs
// merge settings
var settings = {
...defaults,
...options
};
// set base element selector
if (settings.selector != null) {
parentSelector = document.querySelector(settings.selector)
} else {
parentSelector = document;
}
// elements
childEnamis = parentSelector.querySelectorAll('[data-enami]')
parentEnamis = parentSelector.querySelectorAll('[data-enami-children]');
// reset method
// enami.reset = function (element) {
// emitEvent('enami:reset');
// var e = document.querySelector(element);
// enamiteReset(e)
// }
// update method
// enami.update = function () {
// emitEvent('enami:update');
// enami.destroy();
// init();
// }
// destroy method
enami.destroy = function (state = null) {
// enami.destroy = function (element = null, state = null) {
if (observer == null) {
console.error("enami.js: You can destroy a not initialized enami")
return;
}
emitEvent('enami:destroy', null, {
detail: {
// target: (element != null ? document.querySelector(element) : settings.selector)
target: settings.selector
}
});
// if (element != null) {
// var e = document.querySelector(element);
// enamiteReset(e)
// }
observer.disconnect();
observer = null;
if (state == 'initial') {
childEnamis.forEach(entry => {
entry.removeAttribute('data-enami-out')
entry.removeAttribute('data-enami-in')
});
} else if (state == 'final') {
childEnamis.forEach(entry => {
entry.removeAttribute('data-enami-out')
entry.setAttribute('data-enami-in', '')
});
}
}
function init() {
emitEvent('enami:init', null, {
detail: {
target: (settings.selector == null ? document : settings.selector)
}
});
setupStyles();
// disable on mobile
if (settings.disableOnMobile == true && isMobile()) {
return;
}
// set intersection observers
observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
var isParentEnami = entry.target.hasAttribute('data-enami-children');
if (isParentEnami) {
let parentStagger = entry.target.getAttribute('data-enami-stagger');
let parentReset = entry.target.getAttribute('data-enami-reset');
let childrenClass = entry.target.getAttribute('data-enami-children');
let childrens = entry.target.querySelectorAll(childrenClass);
let entryDelay = entry.target.getAttribute('data-enami-delay');
let childrenAnimation = entry.target.getAttribute('data-enami-animation');
// setup stagger delay
if (parentStagger != null) {
let parentStaggerNumber = secondsToMs(parentStagger);
let i = 1;
if (entryDelay != null) { // getting initial staggering delay
entryDelay = secondsToMs(entryDelay);
} else if (settings.delay != null) {
entryDelay = secondsToMs(settings.delay);
}
// alert(entryDelay);
childrens.forEach(children => {
let delay = parentStaggerNumber * i;
children.style.animationDelay = (delay + entryDelay) + 'ms';
i++;
children.setAttribute('data-enami', childrenAnimation)
});
}
if (entry.isIntersecting) {
childrens.forEach((children, i) => {
enamiteIn(children, settings)
});
// unobserve parent if has once attribute
let dataOnce = entry.target.hasAttribute('data-enami-once');
if (dataOnce || settings.once) {
observer.unobserve(entry.target);
}
} else {
childrens.forEach(children => {
if (children.hasAttribute('data-enami-in')) {
if (parentReset != null || settings.reset) {
enamiteReset(children);
} else {
enamiteOut(children)
}
}
});
}
} else {
// is regular entry
if (entry.isIntersecting) {
enamiteIn(entry.target, settings)
// unobserve if has once attribute
let dataOnce = entry.target.hasAttribute('data-enami-once');
if (dataOnce || settings.once) {
observer.unobserve(entry.target);
}
} else if (entry.target.hasAttribute('data-enami-in') && entry.target.hasAttribute('data-enami-reset') == false) {
enamiteOut(entry.target)
} else if (entry.target.hasAttribute('data-enami-reset')) {
enamiteReset(entry.target)
}
}
});
}, {
rootMargin: settings.offset,
threshold: settings.threshold,
root: settings.root,
});
// add to observer each single enami
childEnamis.forEach(enamis => { observer.observe(enamis) });
// add to observer each parent enami
parentEnamis.forEach(parent => {
observer.observe(parent);
// remove normal observer from childrens
let childrenClass = parent.getAttribute('data-enami-children');
let childrens = parent.querySelectorAll(childrenClass);
childrens.forEach(children => {
observer.unobserve(children);
});
});
}
init();
return enami;
};
return enami;
});