119 lines
2.7 KiB
TypeScript
119 lines
2.7 KiB
TypeScript
import { Directive } from "vue";
|
|
|
|
type Value = { max?: number[]; min?: number[] };
|
|
|
|
//const observers = new Map<Element, ResizeObserver>();
|
|
const mountings = new Map<
|
|
Element,
|
|
{
|
|
value: Value;
|
|
resize: ResizeObserver;
|
|
intersection?: IntersectionObserver;
|
|
previousWidth: number;
|
|
}
|
|
>();
|
|
|
|
type ClassOrder = {
|
|
add: string[];
|
|
remove: string[];
|
|
};
|
|
|
|
const cache = new Map<string, ClassOrder>();
|
|
|
|
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<Element, Value>;
|