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'; | ||||
| 
 | ||||
| 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 { | ||||
| 	mounted(src, binding, vn) { | ||||
| 		const calc = () => { | ||||
| 			const height = src.clientHeight; | ||||
| 			const width = src.clientWidth; | ||||
| 
 | ||||
| 			// 要素が(一時的に)DOMに存在しないときは計算スキップ
 | ||||
| 			if (height === 0) return; | ||||
| 
 | ||||
| 			binding.value(width, height); | ||||
| 		}; | ||||
| 
 | ||||
| 		calc(); | ||||
| 
 | ||||
| 		// Vue3では使えなくなった
 | ||||
| 		// 無くても大丈夫か...?
 | ||||
| 		// TODO: ↑大丈夫じゃなかったので解決策を探す
 | ||||
| 		//vn.context.$on('hook:activated', calc);
 | ||||
| 
 | ||||
| 		const ro = new ResizeObserver((entries, observer) => { | ||||
| 			calc(); | ||||
| 		const resize = new ResizeObserver((entries, observer) => { | ||||
| 			calc(src); | ||||
| 		}); | ||||
| 		ro.observe(src); | ||||
| 		resize.observe(src); | ||||
| 
 | ||||
| 		src._get_size_ro_ = ro; | ||||
| 		mountings.set(src, { resize, fn: binding.value, }); | ||||
| 		calc(src); | ||||
| 	}, | ||||
| 
 | ||||
| 	unmounted(src, binding, vn) { | ||||
| 		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'; | ||||
| 
 | ||||
| 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() | ||||
| 		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 { | ||||
| 	mounted(src, binding, vn) { | ||||
| 		const query = binding.value; | ||||
| 		const resize = new ResizeObserver((entries, observer) => { | ||||
| 			calc(src); | ||||
| 		}); | ||||
| 
 | ||||
| 		const addClass = (el: Element, cls: string) => { | ||||
| 			el.classList.add(cls); | ||||
| 		}; | ||||
| 		mountings.set(src, { | ||||
| 			value: binding.value, | ||||
| 			resize, | ||||
| 			previousWidth: 0, | ||||
| 		}); | ||||
| 
 | ||||
| 		const removeClass = (el: Element, cls: string) => { | ||||
| 			el.classList.remove(cls); | ||||
| 		}; | ||||
| 		calc(src); | ||||
| 		resize.observe(src); | ||||
| 	}, | ||||
| 
 | ||||
| 		const calc = () => { | ||||
| 			const width = src.clientWidth; | ||||
| 
 | ||||
| 			// 要素が(一時的に)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; | ||||
| 	updated(src, binding, vn) { | ||||
| 		mountings.set(src, Object.assign({}, mountings.get(src), { value: binding.value })); | ||||
| 		calc(src); | ||||
| 	}, | ||||
| 
 | ||||
| 	unmounted(src, binding, vn) { | ||||
| 		//el._ro_.unobserve(el);
 | ||||
| 		window.removeEventListener('resize', src._calc_); | ||||
| 		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, Value>; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue