fix: v-sizeディレクティブの動作を修正 (#8249)
* Fix size directive behavior not activated * calc * wip * cache computed classes * fix Vue3では使えなくなった * 不要なIntersection Observerを削除 * comment
This commit is contained in:
parent
58fa54a9a6
commit
6cbd66b534
2 changed files with 137 additions and 77 deletions
|
@ -1,34 +1,55 @@
|
||||||
import { Directive } from 'vue';
|
import { Directive } from 'vue';
|
||||||
|
|
||||||
|
const mountings = new Map<Element, {
|
||||||
|
resize: ResizeObserver;
|
||||||
|
intersection?: IntersectionObserver;
|
||||||
|
fn: (w: number, h: number) => void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
function calc(src: Element) {
|
||||||
|
const info = mountings.get(src);
|
||||||
|
const height = src.clientHeight;
|
||||||
|
const width = src.clientWidth;
|
||||||
|
|
||||||
|
if (!info) return;
|
||||||
|
|
||||||
|
// アクティベート前などでsrcが描画されていない場合
|
||||||
|
if (!height) {
|
||||||
|
// IntersectionObserverで表示検出する
|
||||||
|
if (!info.intersection) {
|
||||||
|
info.intersection = new IntersectionObserver(entries => {
|
||||||
|
if (entries.some(entry => entry.isIntersecting)) calc(src);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
info.intersection.observe(src);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (info.intersection) {
|
||||||
|
info.intersection.disconnect()
|
||||||
|
delete info.intersection;
|
||||||
|
};
|
||||||
|
|
||||||
|
info.fn(width, height);
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mounted(src, binding, vn) {
|
mounted(src, binding, vn) {
|
||||||
const calc = () => {
|
|
||||||
const height = src.clientHeight;
|
|
||||||
const width = src.clientWidth;
|
|
||||||
|
|
||||||
// 要素が(一時的に)DOMに存在しないときは計算スキップ
|
const resize = new ResizeObserver((entries, observer) => {
|
||||||
if (height === 0) return;
|
calc(src);
|
||||||
|
|
||||||
binding.value(width, height);
|
|
||||||
};
|
|
||||||
|
|
||||||
calc();
|
|
||||||
|
|
||||||
// Vue3では使えなくなった
|
|
||||||
// 無くても大丈夫か...?
|
|
||||||
// TODO: ↑大丈夫じゃなかったので解決策を探す
|
|
||||||
//vn.context.$on('hook:activated', calc);
|
|
||||||
|
|
||||||
const ro = new ResizeObserver((entries, observer) => {
|
|
||||||
calc();
|
|
||||||
});
|
});
|
||||||
ro.observe(src);
|
resize.observe(src);
|
||||||
|
|
||||||
src._get_size_ro_ = ro;
|
mountings.set(src, { resize, fn: binding.value, });
|
||||||
|
calc(src);
|
||||||
},
|
},
|
||||||
|
|
||||||
unmounted(src, binding, vn) {
|
unmounted(src, binding, vn) {
|
||||||
binding.value(0, 0);
|
binding.value(0, 0);
|
||||||
src._get_size_ro_.unobserve(src);
|
const info = mountings.get(src);
|
||||||
|
if (!info) return;
|
||||||
|
info.resize.disconnect();
|
||||||
|
if (info.intersection) info.intersection.disconnect();
|
||||||
|
mountings.delete(src);
|
||||||
}
|
}
|
||||||
} as Directive;
|
} as Directive<Element, (w: number, h: number) => void>;
|
||||||
|
|
|
@ -1,68 +1,107 @@
|
||||||
import { Directive } from 'vue';
|
import { Directive } from 'vue';
|
||||||
|
|
||||||
|
type Value = { max?: number[]; min?: number[]; };
|
||||||
|
|
||||||
//const observers = new Map<Element, ResizeObserver>();
|
//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()
|
||||||
|
delete info.intersection;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 {
|
export default {
|
||||||
mounted(src, binding, vn) {
|
mounted(src, binding, vn) {
|
||||||
const query = binding.value;
|
const resize = new ResizeObserver((entries, observer) => {
|
||||||
|
calc(src);
|
||||||
|
});
|
||||||
|
|
||||||
const addClass = (el: Element, cls: string) => {
|
mountings.set(src, {
|
||||||
el.classList.add(cls);
|
value: binding.value,
|
||||||
};
|
resize,
|
||||||
|
previousWidth: 0,
|
||||||
|
});
|
||||||
|
|
||||||
const removeClass = (el: Element, cls: string) => {
|
calc(src);
|
||||||
el.classList.remove(cls);
|
resize.observe(src);
|
||||||
};
|
},
|
||||||
|
|
||||||
const calc = () => {
|
updated(src, binding, vn) {
|
||||||
const width = src.clientWidth;
|
mountings.set(src, Object.assign({}, mountings.get(src), { value: binding.value }));
|
||||||
|
calc(src);
|
||||||
// 要素が(一時的に)DOMに存在しないときは計算スキップ
|
|
||||||
if (width === 0) return;
|
|
||||||
|
|
||||||
if (query.max) {
|
|
||||||
for (const v of query.max) {
|
|
||||||
if (width <= v) {
|
|
||||||
addClass(src, 'max-width_' + v + 'px');
|
|
||||||
} else {
|
|
||||||
removeClass(src, 'max-width_' + v + 'px');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (query.min) {
|
|
||||||
for (const v of query.min) {
|
|
||||||
if (width >= v) {
|
|
||||||
addClass(src, 'min-width_' + v + 'px');
|
|
||||||
} else {
|
|
||||||
removeClass(src, 'min-width_' + v + 'px');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
calc();
|
|
||||||
|
|
||||||
window.addEventListener('resize', calc);
|
|
||||||
|
|
||||||
// Vue3では使えなくなった
|
|
||||||
// 無くても大丈夫か...?
|
|
||||||
// TODO: ↑大丈夫じゃなかったので解決策を探す
|
|
||||||
//vn.context.$on('hook:activated', calc);
|
|
||||||
|
|
||||||
//const ro = new ResizeObserver((entries, observer) => {
|
|
||||||
// calc();
|
|
||||||
//});
|
|
||||||
|
|
||||||
//ro.observe(el);
|
|
||||||
|
|
||||||
// TODO: 新たにプロパティを作るのをやめMapを使う
|
|
||||||
// ただメモリ的には↓の方が省メモリかもしれないので検討中
|
|
||||||
//el._ro_ = ro;
|
|
||||||
src._calc_ = calc;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
unmounted(src, binding, vn) {
|
unmounted(src, binding, vn) {
|
||||||
//el._ro_.unobserve(el);
|
const info = mountings.get(src);
|
||||||
window.removeEventListener('resize', src._calc_);
|
if (!info) return;
|
||||||
|
info.resize.disconnect();
|
||||||
|
if (info.intersection) info.intersection.disconnect();
|
||||||
|
mountings.delete(src);
|
||||||
}
|
}
|
||||||
} as Directive;
|
} as Directive<Element, Value>;
|
||||||
|
|
Loading…
Reference in a new issue