import { Directive } from "vue"; type Value = { max?: number[]; min?: number[] }; //const observers = new Map(); const mountings = new Map< Element, { value: Value; resize: ResizeObserver; intersection?: IntersectionObserver; previousWidth: number; } >(); type ClassOrder = { add: string[]; remove: string[]; }; const cache = new Map(); function getClassOrder(width: number, queue: Value): ClassOrder { const getMaxClass = (v: number) => `max-width_${v}px`; const getMinClass = (v: number) => `min-width_${v}px`; return { add: [ ...(queue.max ? queue.max.filter((v) => width <= v).map(getMaxClass) : []), ...(queue.min ? queue.min.filter((v) => width >= v).map(getMinClass) : []), ], remove: [ ...(queue.max ? queue.max.filter((v) => width > v).map(getMaxClass) : []), ...(queue.min ? queue.min.filter((v) => width < v).map(getMinClass) : []), ], }; } function applyClassOrder(el: Element, order: ClassOrder) { el.classList.add(...order.add); el.classList.remove(...order.remove); } function getOrderName(width: number, queue: Value): string { return `${width}|${queue.max ? queue.max.join(",") : ""}|${ queue.min ? queue.min.join(",") : "" }`; } function calc(el: Element) { const info = mountings.get(el); const width = el.clientWidth; if (!info || info.previousWidth === width) return; // アクティベート前などでsrcが描画されていない場合 if (!width) { // IntersectionObserverで表示検出する if (!info.intersection) { info.intersection = new IntersectionObserver((entries) => { if (entries.some((entry) => entry.isIntersecting)) calc(el); }); } info.intersection.observe(el); return; } if (info.intersection) { info.intersection.disconnect(); info.intersection = undefined; } mountings.set(el, Object.assign(info, { previousWidth: width })); const cached = cache.get(getOrderName(width, info.value)); if (cached) { applyClassOrder(el, cached); } else { const order = getClassOrder(width, info.value); cache.set(getOrderName(width, info.value), order); applyClassOrder(el, order); } } export default { mounted(src, binding, vn) { const resize = new ResizeObserver((entries, observer) => { calc(src); }); mountings.set(src, { value: binding.value, resize, previousWidth: 0, }); calc(src); resize.observe(src); }, updated(src, binding, vn) { mountings.set( src, Object.assign({}, mountings.get(src), { value: binding.value }), ); calc(src); }, unmounted(src, binding, vn) { const info = mountings.get(src); if (!info) return; info.resize.disconnect(); if (info.intersection) info.intersection.disconnect(); mountings.delete(src); }, } as Directive;