wow.js配合第三方动画库可以让元素出现在屏幕时执行相关的动画
- wow.js依赖于animate.css,首先在头部引用animate.css或者animate.min.css。
- 在最底部或引入的css下面引用wow.js或者wow.min.js
- 使用规则直接查看官网:wow.js — Reveal Animations When Scrolling
问题:wow.js动画官网的代码默认只会执行一次,而有的时候我们想控制某些元素在滚动条滚动时只要该元素出现在屏幕上就执行对应的动画。我们需对源码的scrollCallback函数进行一定的改造即可:
<div class="example-box wow fadeInUp" data-wow-infinite="true">
<p>执行动画的元素,必须有class="wow 某个动画名称类"</p>
<p>data-wow-infinite="true" 加上该属性并将值设为true,则每次元素进入屏幕就会执行一次动画</p>
<p>data-wow-infinite="false" 不加或者值设为false的话只会在初次进入屏幕时执行一次动画</p>
</div>
//滚动后的回调函数
scrollCallback() {
if (this.scrolled) {
this.scrolled = false;
const results = [];
for (let i = 0; i < this.boxes.length; i++) {
const box = this.boxes[i];
if (box) {
const infinite = box.getAttribute('data-wow-infinite');
if (this.isVisible(box)) {
this.show(box);
if (!infinite) continue;
} else {
if (infinite) this.customStyle(box, true)
}
results.push(box);
}
}
this.boxes = results;
if (!this.boxes.length && !this.config.live) {
this.stop();
}
}
}
//wow.js优化后的源码
function isIn(needle, haystack) {
return haystack.indexOf(needle) >= 0;
}
function extend(custom, defaults) {
for (const key in defaults) {
if (custom[key] == null) {
const value = defaults[key];
custom[key] = value;
}
}
return custom;
}
function isMobile(agent) {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(agent);
}
function createEvent(event, bubble = false, cancel = false, detail = null) {
let customEvent;
if (document.createEvent != null) { // W3C DOM
customEvent = document.createEvent('CustomEvent');
customEvent.initCustomEvent(event, bubble, cancel, detail);
} else if (document.createEventObject != null) { // IE DOM < 9
customEvent = document.createEventObject();
customEvent.eventType = event;
} else {
customEvent.eventName = event;
}
return customEvent;
}
function emitEvent(elem, event) {
if (elem.dispatchEvent != null) { // W3C DOM
elem.dispatchEvent(event);
} else if (event in (elem != null)) {
elem[event]();
} else if (`on${event}` in (elem != null)) {
elem[`on${event}`]();
}
}
function addEvent(elem, event, fn) {
if (elem.addEventListener != null) { // W3C DOM
elem.addEventListener(event, fn, false);
} else if (elem.attachEvent != null) { // IE DOM
elem.attachEvent(`on${event}`, fn);
} else { // fallback
elem[event] = fn;
}
}
function removeEvent(elem, event, fn) {
if (elem.removeEventListener != null) { // W3C DOM
elem.removeEventListener(event, fn, false);
} else if (elem.detachEvent != null) { // IE DOM
elem.detachEvent(`on${event}`, fn);
} else { // fallback
delete elem[event];
}
}
function getInnerHeight() {
if ('innerHeight' in window) {
return window.innerHeight;
}
return document.documentElement.clientHeight;
}
// Minimalistic WeakMap shim, just in case.
const WeakMap = window.WeakMap || window.MozWeakMap ||
class WeakMap {
constructor() {
this.keys = [];
this.values = [];
}
get(key) {
for (let i = 0; i < this.keys.length; i++) {
const item = this.keys[i];
if (item === key) {
return this.values[i];
}
}
return undefined;
}
set(key, value) {
for (let i = 0; i < this.keys.length; i++) {
const item = this.keys[i];
if (item === key) {
this.values[i] = value;
return this;
}
}
this.keys.push(key);
this.values.push(value);
return this;
}
};
// Dummy MutationObserver, to avoid raising exceptions.
const MutationObserver =
window.MutationObserver || window.WebkitMutationObserver ||
window.MozMutationObserver ||
class MutationObserver {
constructor() {
if (typeof console !== 'undefined' && console !== null) {
console.warn('MutationObserver is not supported by your browser.');
console.warn(
'WOW.js cannot detect dom mutations, please call .sync() after loading new content.'
);
}
}
static notSupported = true;
observe() { }
};
// getComputedStyle shim, from http://stackoverflow.com/a/21797294
const getComputedStyle = window.getComputedStyle ||
function getComputedStyle(el) {
const getComputedStyleRX = /(\-([a-z]){1})/g;
return {
getPropertyValue(prop) {
if (prop === 'float') { prop = 'styleFloat'; }
if (getComputedStyleRX.test(prop)) {
prop.replace(getComputedStyleRX, (_, _char) => _char.toUpperCase());
}
const { currentStyle } = el;
return (currentStyle != null ? currentStyle[prop] : void 0) || null;
},
};
};
class WOW {
defaults = {
boxClass: 'wow',
animateClass: 'animated',
offset: 0,
mobile: true,
live: true,
callback: null,
scrollContainer: null,
resetAnimation: true,
};
constructor(options = {}) {
this.start = this.start.bind(this);
this.resetAnimation = this.resetAnimation.bind(this);
this.scrollHandler = this.scrollHandler.bind(this);
this.scrollCallback = this.scrollCallback.bind(this);
this.scrolled = true;
this.config = extend(options, this.defaults);
if (options.scrollContainer != null) {
this.config.scrollContainer = document.querySelector(options.scrollContainer);
}
// Map of elements to animation names:
this.animationNameCache = new WeakMap();
this.wowEvent = createEvent(this.config.boxClass);
}
init() {
this.element = window.document.documentElement;
if (isIn(document.readyState, ['interactive', 'complete'])) {
this.start();
} else {
addEvent(document, 'DOMContentLoaded', this.start);
}
this.finished = [];
}
start() {
this.stopped = false;
this.boxes = [].slice.call(this.element.querySelectorAll(`.${this.config.boxClass}`));
this.all = this.boxes.slice(0);
if (this.boxes.length) {
if (this.disabled()) {
this.resetStyle();
} else {
for (let i = 0; i < this.boxes.length; i++) {
const box = this.boxes[i];
this.applyStyle(box, true);
}
}
}
if (!this.disabled()) {
addEvent(this.config.scrollContainer || window, 'scroll', this.scrollHandler);
addEvent(window, 'resize', this.scrollHandler);
this.interval = setInterval(this.scrollCallback, 50);
}
if (this.config.live) {
const mut = new MutationObserver(records => {
for (let j = 0; j < records.length; j++) {
const record = records[j];
for (let k = 0; k < record.addedNodes.length; k++) {
const node = record.addedNodes[k];
this.doSync(node);
}
}
return undefined;
});
mut.observe(document.body, {
childList: true,
subtree: true,
});
}
}
// unbind the scroll event
stop() {
this.stopped = true;
removeEvent(this.config.scrollContainer || window, 'scroll', this.scrollHandler);
removeEvent(window, 'resize', this.scrollHandler);
if (this.interval != null) {
clearInterval(this.interval);
}
}
sync() {
if (MutationObserver.notSupported) {
this.doSync(this.element);
}
}
doSync(element) {
if (typeof element === 'undefined' || element === null) { ({ element } = this); }
if (element.nodeType !== 1) { return; }
element = element.parentNode || element;
const iterable = element.querySelectorAll(`.${this.config.boxClass}`);
for (let i = 0; i < iterable.length; i++) {
const box = iterable[i];
if (!isIn(box, this.all)) {
this.boxes.push(box);
this.all.push(box);
if (this.stopped || this.disabled()) {
this.resetStyle();
} else {
this.applyStyle(box, true);
}
this.scrolled = true;
}
}
}
// show box element
show(box) {
this.applyStyle(box);
box.className = `${box.className} ${this.config.animateClass}`;
if (this.config.callback != null) { this.config.callback(box); }
emitEvent(box, this.wowEvent);
if (this.config.resetAnimation) {
addEvent(box, 'animationend', this.resetAnimation);
addEvent(box, 'oanimationend', this.resetAnimation);
addEvent(box, 'webkitAnimationEnd', this.resetAnimation);
addEvent(box, 'MSAnimationEnd', this.resetAnimation);
}
return box;
}
applyStyle(box, hidden) {
const duration = box.getAttribute('data-wow-duration');
const delay = box.getAttribute('data-wow-delay');
const iteration = box.getAttribute('data-wow-iteration');
return this.animate(() => this.customStyle(box, hidden, duration, delay, iteration));
}
animate = (function animateFactory() {
if ('requestAnimationFrame' in window) {
return callback => window.requestAnimationFrame(callback);
}
return callback => callback();
}());
resetStyle() {
for (let i = 0; i < this.boxes.length; i++) {
const box = this.boxes[i];
box.style.visibility = 'visible';
}
return undefined;
}
resetAnimation(event) {
if (event.type.toLowerCase().indexOf('animationend') >= 0) {
const target = event.target || event.srcElement;
target.className = target.className.replace(this.config.animateClass, '').trim();
}
}
customStyle(box, hidden, duration, delay, iteration) {
if (hidden) { this.cacheAnimationName(box); }
box.style.visibility = hidden ? 'hidden' : 'visible';
if (duration) { this.vendorSet(box.style, { animationDuration: duration }); }
if (delay) { this.vendorSet(box.style, { animationDelay: delay }); }
if (iteration) { this.vendorSet(box.style, { animationIterationCount: iteration }); }
this.vendorSet(box.style, { animationName: hidden ? 'none' : this.cachedAnimationName(box) });
return box;
}
vendors = ['moz', 'webkit'];
vendorSet(elem, properties) {
for (const name in properties) {
if (properties.hasOwnProperty(name)) {
const value = properties[name];
elem[`${name}`] = value;
for (let i = 0; i < this.vendors.length; i++) {
const vendor = this.vendors[i];
elem[`${vendor}${name.charAt(0).toUpperCase()}${name.substr(1)}`] = value;
}
}
}
}
vendorCSS(elem, property) {
const style = getComputedStyle(elem);
let result = style.getPropertyCSSValue(property);
for (let i = 0; i < this.vendors.length; i++) {
const vendor = this.vendors[i];
result = result || style.getPropertyCSSValue(`-${vendor}-${property}`);
}
return result;
}
animationName(box) {
let aName;
try {
aName = this.vendorCSS(box, 'animation-name').cssText;
} catch (error) { // Opera, fall back to plain property value
aName = getComputedStyle(box).getPropertyValue('animation-name');
}
if (aName === 'none') {
return ''; // SVG/Firefox, unable to get animation name?
}
return aName;
}
cacheAnimationName(box) {
// https://bugzilla.mozilla.org/show_bug.cgi?id=921834
// box.dataset is not supported for SVG elements in Firefox
return this.animationNameCache.set(box, this.animationName(box));
}
cachedAnimationName(box) {
return this.animationNameCache.get(box);
}
// fast window.scroll callback
scrollHandler() {
this.scrolled = true;
}
scrollCallback() {
if (this.scrolled) {
this.scrolled = false;
const results = [];
for (let i = 0; i < this.boxes.length; i++) {
const box = this.boxes[i];
if (box) {
const infinite = box.getAttribute('data-wow-infinite');
if (this.isVisible(box)) {
this.show(box);
if (!infinite) continue;
} else {
if (infinite) this.customStyle(box, true)
}
results.push(box);
}
}
this.boxes = results;
if (!this.boxes.length && !this.config.live) {
this.stop();
}
}
}
// Calculate element offset top
offsetTop(element) {
// SVG elements don't have an offsetTop in Firefox.
// This will use their nearest parent that has an offsetTop.
// Also, using ('offsetTop' of element) causes an exception in Firefox.
while (element.offsetTop === undefined) {
element = element.parentNode;
}
let top = element.offsetTop;
while (element.offsetParent) {
element = element.offsetParent;
top += element.offsetTop;
}
return top;
}
// check if box is visible
isVisible(box) {
const offset = box.getAttribute('data-wow-offset') || this.config.offset;
const viewTop = (
this.config.scrollContainer && this.config.scrollContainer.scrollTop
) || window.pageYOffset;
const viewBottom =
viewTop + Math.min(this.element.clientHeight, getInnerHeight()) - offset;
const top = this.offsetTop(box);
const bottom = top + box.clientHeight;
return top <= viewBottom && bottom >= viewTop;
}
disabled() {
return !this.config.mobile && isMobile(navigator.userAgent);
}
}