// @ts-check
///
///
///
{
/** @type {JQueryStatic} */
// @ts-ignore
const $ = typeof jQuery !== "undefined" ? jQuery : null;
const {typesTesters: typesTesters, typesParsers: typesParsers} = dataAttrHelpers;
/**
*
* @param {string} dataAttr
* @param {Record} types
*/ const parseDataAttr = (dataAttr, types) => {
const options = {};
for (const row of dataAttr.split(";")) {
const match = row.trim().match(/^(.*?):([\s\S]*)$/);
if (!match) continue;
let [key, value] = [ match[1], match[2] ].map(a => a.trim());
if (aliases[key]) {
key = aliases[key];
}
for (const type in types) {
if (types[type].includes(key) && typesTesters[type](value)) {
value = typesParsers[type](value);
break;
}
}
if (typeof value === "string") {
if (/^(\[|\{|anime\.|"|')/.test(value)) {
value = new Function(`return (${value})`)();
}
}
const parts = key.split("-");
/** @type {any} */ let opts = options;
parts.forEach((part, i) => {
if (i < parts.length - 1) {
opts[part] = opts[part] || {};
opts = opts[part];
} else {
opts[part] = value;
}
});
}
return options;
};
/** @type {Record} */ const dataAnimeAttrType = {
string: [ "targets", "onscroll", "onscroll-target" ],
number: [ "onview" ],
boolean: [ "loop", "onclick", "onview", "autoplay", "onscroll", "onscroll-target", "onscroll-pen" ]
};
/** @type {Record} */ const aliases = {
onscroll: "onscroll-target",
"onscroll-trigger": "onscroll-triggerHook"
};
/**
*
* @param {Element & { animeToggleOpen?: boolean }} el
* @param {anime.AnimeInstance | anime.AnimeTimelineInstance} instance
* @param {'restart' | 'alternate' | 'reset'} direction
*/ const runInstance = (el, instance, direction = "restart") => {
if (direction === "alternate") {
if (!el.animeToggleOpen) {
if (instance.reversed) {
instance.reverse();
}
} else {
if (!instance.reversed) {
instance.reverse();
}
}
el.animeToggleOpen = !el.animeToggleOpen;
instance.play();
} else if (direction === "restart") {
instance.restart();
} else if (direction === "reset") {
instance.reset();
} else {
throw "invalid direction";
}
};
const loadingPromise = new Promise(r => {
document.addEventListener("DOMContentLoaded", e => {
setTimeout(() => {
r(true);
}, 1e3 + 300);
});
});
/**
* @param {Element} el
* @param {AnimeHelperRunOptions} options
* @param {anime.AnimeInstance | anime.AnimeTimelineInstance} instance
*/ const runHelper = async (el, options, instance) => {
/**
*
* @param {Parameters[2]} direction
*/
const run = (direction = "restart") => {
runInstance(el, instance, direction);
};
let autoRun = options.autoplay !== false;
if (options.onclick) {
const toggle = options.onclick === "alternate";
el.addEventListener("click", e => {
e.preventDefault();
run(toggle ? "alternate" : "restart");
});
autoRun = false;
}
if (options.onscroll) {
const scrollMagicOptions = typeof options.onscroll === "boolean" ? {
triggerElement: el
} : options.onscroll;
if (scrollMagicOptions.target) {
scrollMagicOptions.triggerElement = scrollMagicOptions.target;
delete scrollMagicOptions.target;
}
const controller = new ScrollMagic.Controller({
...scrollMagicOptions.controller || {}
});
const penOption = scrollMagicOptions.pen;
delete scrollMagicOptions.controller;
delete scrollMagicOptions.pen;
const sceneOptions = scrollMagicOptions || {};
const triggerElement = sceneOptions.triggerElement ? typeof sceneOptions.triggerElement === "string" ? document.querySelector(sceneOptions.triggerElement) : sceneOptions.triggerElement : el;
delete sceneOptions.triggerElement;
const scene = new ScrollMagic.Scene({
triggerElement: triggerElement,
duration: "100%",
triggerHook: 1,
...sceneOptions
});
if (penOption) {
const pen = penOption === true ? triggerElement : document.querySelector(penOption);
scene.setPin(pen);
}
scene.on("progress", e => {
// @ts-ignore
instance.seek(e.progress * instance.duration);
}).addTo(controller);
void scene;
delete options.onscroll;
autoRun = false;
}
if (options.onhover) {
$(el).on("mouseenter mouseleave", () => {
run("alternate");
});
autoRun = false;
}
await loadingPromise;
if (typeof options.onview !== "undefined" && options.onview !== false) {
const offset = typeof options.onview === "number" ? options.onview : 0;
const handler = () => {
if (window.innerHeight > el.getBoundingClientRect().top - offset) {
window.removeEventListener("scroll", handler);
window.removeEventListener("resize", handler);
run();
}
};
window.addEventListener("scroll", handler);
window.addEventListener("resize", handler);
handler();
autoRun = false;
}
if (options.media) {
const mediaArray = typeof options.media === "string" ? [ options.media ] : options.media;
/** @type {{ bp: string, type: 'min' | 'max' }[][]} */ const mediaList = [];
for (let i = 0; i < mediaArray.length; i++) {
const bp = mediaArray[i];
const group = i % 2 === 0 ? mediaList[i / 2] = [] : mediaList[(i - 1) / 2];
group.push({
bp: bp,
type: i % 2 === 0 ? "min" : "max"
});
}
const mediaText = mediaList.map(mediaArray => "(" + mediaArray.map(({bp: bp, type: type}) => {
const bpSize = breakpoints[bp] || 0;
return `(${type}-width: ${type === "max" ? bpSize - 1 : bpSize}px)`;
}).join(" and ") + ")").join(" or ");
// console.log('media', mediaArray, mediaList, mediaText);
const media = matchMedia(mediaText);
const update = () => {
// console.log('media.matches', media.matches);
run(media.matches ? "restart" : "reset");
};
media.onchange = update;
update();
autoRun = false;
}
if (autoRun) {
run();
}
};
/** @param {Element} el */ const runDataAnime = async el => {
/** @type {AnimeHelperRunOptions} */
const options = parseDataAttr(el.getAttribute("data-anime") || "", dataAnimeAttrType);
const targets = options.targets ? [ ...$(options.targets, el) ] : el;
Object.assign(options, {
targets: targets
});
/** @type {anime.AnimeTimelineInstance | anime.AnimeInstance} */ let instance;
if (options.timeline) {
const timelineName = options.timeline;
delete options.timeline;
if (!timelines[timelineName]) {
if (!timelinesPromises[timelineName]) {
timelinesPromises[timelineName] = new Promise(resolve => {
timelinesResolvers[timelineName] = resolve;
});
}
await timelinesPromises[timelineName];
}
instance = timelines[timelineName](el, options);
} else {
instance = anime(options);
}
instance.pause();
// @ts-ignore
el.animeInstance = instance;
runHelper(el, options, instance);
};
/** @type {Record} */ const timelines = {};
/** @type {Record>} */ const timelinesPromises = {};
/** @type {Record void>} */ const timelinesResolvers = {};
/**
*
* @param {string} name
* @param {AnimeTimelineHelperCallback} fn
*/ const defineAnimeTimelineHelper = (name, fn) => {
timelines[name] = fn;
if (timelinesResolvers[name]) {
timelinesResolvers[name](fn);
}
};
Object.assign(window, {
defineAnimeTimelineHelper: defineAnimeTimelineHelper
});
/** @param {Element} el */ const runDataAnimeToggle = async el => {
const toggleSelector = el.getAttribute("data-anime-toggle") || "";
el.addEventListener("click", e => {
e.preventDefault();
const els = [ ...$(toggleSelector) ];
els.forEach(other => {
/** @type {anime.AnimeTimelineInstance | anime.AnimeInstance} */
// @ts-ignore
const instance = other.animeTimelineInstance || other.animeInstance;
if (!instance) return;
runInstance(other, instance, "alternate");
});
});
};
// Run all.
dataAttrHelpers.watchDataAttr("data-anime", runDataAnime);
dataAttrHelpers.watchDataAttr("data-anime-toggle", runDataAnimeToggle);
}