Skip to content

Instantly share code, notes, and snippets.

@GLWalker
Created January 1, 2025 03:27
Show Gist options
  • Save GLWalker/6204e526cea406e6afc37c8b4746b115 to your computer and use it in GitHub Desktop.
Save GLWalker/6204e526cea406e6afc37c8b4746b115 to your computer and use it in GitHub Desktop.
Wow JS Modernized
;(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["module", "exports"], factory)
} else if (typeof exports !== "undefined") {
factory(module, exports)
} else {
const mod = { exports: {} }
factory(mod, mod.exports)
global.WOW = mod.exports
}
})(this, function (module, exports) {
"use strict"
// Use ES6+ syntax
const isIn = (needle, haystack) => haystack.includes(needle)
const extend = (custom, defaults) => {
return { ...defaults, ...custom }
}
const isMobile = (agent) =>
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
agent
)
const createEvent = (
event,
bubble = false,
cancel = false,
detail = null
) => {
const customEvent = new CustomEvent(event, {
bubbles: bubble,
cancelable: cancel,
detail,
})
return customEvent
}
const emitEvent = (elem, event) => {
elem.dispatchEvent(event)
}
const addEvent = (elem, event, fn) => {
elem.addEventListener(event, fn, false)
}
const removeEvent = (elem, event, fn) => {
elem.removeEventListener(event, fn, false)
}
const getInnerHeight = () =>
window.innerHeight || document.documentElement.clientHeight
// MutationObserver is natively supported in modern browsers
const MutationObserver =
window.MutationObserver ||
window.WebkitMutationObserver ||
window.MozMutationObserver
const WOW = class {
constructor(options = {}) {
this.defaults = {
boxClass: "wow",
animateClass: "animated",
offset: 0,
mobile: true,
live: true,
callback: null,
scrollContainer: null,
resetAnimation: true,
}
this.animate = window.requestAnimationFrame
? (callback) => window.requestAnimationFrame(callback)
: (callback) => callback()
this.vendors = ["moz", "webkit"]
this.config = { ...this.defaults, ...options }
if (options.scrollContainer) {
this.config.scrollContainer = document.querySelector(
options.scrollContainer
)
}
this.animationNameCache = new WeakMap()
this.wowEvent = createEvent(this.config.boxClass)
this.start = this.start.bind(this)
this.resetAnimation = this.resetAnimation.bind(this)
this.scrollHandler = this.scrollHandler.bind(this)
this.scrollCallback = this.scrollCallback.bind(this)
}
init() {
this.element = document.documentElement
if (["interactive", "complete"].includes(document.readyState)) {
this.start()
} else {
addEvent(document, "DOMContentLoaded", this.start)
}
this.finished = []
}
start() {
this.stopped = false
this.boxes = Array.from(
this.element.querySelectorAll(`.${this.config.boxClass}`)
)
this.all = [...this.boxes]
if (this.boxes.length) {
if (this.disabled()) {
this.resetStyle()
} else {
this.boxes.forEach((box) => 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) => {
records.forEach((record) => {
record.addedNodes.forEach((node) => this.doSync(node))
})
})
mut.observe(document.body, { childList: true, subtree: true })
}
}
stop() {
this.stopped = true
removeEvent(
this.config.scrollContainer || window,
"scroll",
this.scrollHandler
)
removeEvent(window, "resize", this.scrollHandler)
if (this.interval) {
clearInterval(this.interval)
}
}
sync() {
if (MutationObserver.notSupported) {
this.doSync(this.element)
}
}
doSync(element = this.element) {
if (element.nodeType !== 1) return
const iterable = element.querySelectorAll(
`.${this.config.boxClass}`
)
iterable.forEach((box) => {
if (!this.all.includes(box)) {
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) {
this.applyStyle(box)
box.classList.add(this.config.animateClass)
if (this.config.callback) this.config.callback(box)
emitEvent(box, this.wowEvent)
if (this.config.resetAnimation) {
;[
"animationend",
"oanimationend",
"webkitAnimationEnd",
"MSAnimationEnd",
].forEach((event) => addEvent(box, event, 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")
this.animate(() =>
this.customStyle(box, hidden, duration, delay, iteration)
)
}
resetStyle() {
this.boxes.forEach((box) => {
box.style.visibility = "visible"
})
}
resetAnimation(event) {
if (event.type.toLowerCase().includes("animationend")) {
const target = event.target || event.srcElement
target.classList.remove(this.config.animateClass)
}
}
customStyle(box, hidden, duration, delay, iteration) {
if (hidden) {
this.cacheAnimationName(box)
}
box.style.visibility = hidden ? "hidden" : "visible"
if (duration) {
box.style.animationDuration = duration
}
if (delay) {
box.style.animationDelay = delay
}
if (iteration) {
box.style.animationIterationCount = iteration
}
box.style.animationName = hidden
? "none"
: this.cachedAnimationName(box)
return box
}
vendorSet(elem, properties) {
for (const [name, value] of Object.entries(properties)) {
elem[name] = value
this.vendors.forEach((vendor) => {
elem[
`${vendor}${name.charAt(0).toUpperCase()}${name.slice(
1
)}`
] = value
})
}
}
animationName(box) {
try {
return getComputedStyle(box).animationName || "none"
} catch {
return ""
}
}
cacheAnimationName(box) {
this.animationNameCache.set(box, this.animationName(box))
}
cachedAnimationName(box) {
return this.animationNameCache.get(box)
}
scrollHandler() {
this.scrolled = true
}
scrollCallback() {
if (this.scrolled) {
this.scrolled = false
this.boxes = this.boxes.filter((box) => {
if (this.isVisible(box)) {
this.show(box)
return false
}
return true
})
if (!this.boxes.length && !this.config.live) {
this.stop()
}
}
}
isVisible(box) {
const rect = box.getBoundingClientRect()
const windowHeight = getInnerHeight()
const top = rect.top
return top <= windowHeight && top + rect.height >= 0
}
disabled() {
return !this.config.mobile && isMobile(navigator.userAgent)
}
}
// Expose WOW class globally
exports.WOW = WOW
// Auto-initialize WOW when the script is loaded
if (typeof window !== "undefined") {
window.WOW = WOW
}
// Initialize wow.js on page load
if (
document.readyState === "interactive" ||
document.readyState === "complete"
) {
new WOW().init()
} else {
window.addEventListener("DOMContentLoaded", function () {
new WOW().init()
})
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment