PageSourceSearch

https://ftnews.jp/static/lib/js/jquery-onscrolledintoview.js?260929023659

js ftnews.jp collected 2026-10-04 03:07:51 UTC 9,444 bytes, 332 lines download raw bytes

1/**
2 * 'scrolledintoview', 'scrolledoutofview' events.
3 * Scrolled into/outof view for elements/selectors
4 * 
5 * // detect elment is in view
6 * $elm.isInView() : true/false
7 * 
8 * // enable watching scroll into/outof view for elements
9 * $.registerOnScrolledIntoView(elm)
10 * $.registerOnScrolledIntoView($elm)
11 * $.registerOnScrolledIntoView('selector')
12 * 
13 * // on scrolled into view
14 * $elm.on('scrolledintoview', function(){})
15 * $(document).on('scrolledintoview', 'selector', function(){});
16 *
17 * // on scrolled out of view
18 * $elm.on('scrolledoutofview', function(){})
19 * $(document).on('scrolledoutofview', 'selector', function(){});
20 *
21 * $elm.off('scrolledintoview')
22 * $elm.off('scrolledoutofview')
23 * $(document).off('scrolledintoview', 'selector');
24 * $(document).off('scrolledoutofview', 'selector');
25 *              +
26 * // disable watching scroll into/outof view for elements
27 * $.unregisterOnScrolledIntoView(elm)
28 * $.unregisterOnScrolledIntoView($elm)
29 * $.unregisterOnScrolledIntoView('selector')
30 *
31 */
32
33(function($){
34
35var DEBUG = (location.search.indexOf('_cs=0') != -1);
36
37if ($.registerOnScrolledIntoView) { return; }
38console.log('jquery-onscrolledintoview.js loaded.');
39
40var VH = $(window).height();
41$(window).on('resize', function(){
42	VH = $(window).height();
43});
44var ST = $(window).scrollTop(), LAST_ST = ST, SCROLLING_DOWN = false, SCROLLING_UP = false;
45$(window).on('scroll', function(){
46	ST = $(window).scrollTop();
47	SCROLLING_DOWN = ST > LAST_ST;
48	SCROLLING_UP = ST < LAST_ST;
49	LAST_ST = ST;
50});
51
52// offset, height再計算最小間隔ms
53var METRICS_EXPIRE_MS = 100;
54
55$.fn.isInView = function(offset){
56	// offset = window position offset
57	offset = offset === undefined ? 0 : offset;
58	var $elm = $(this),
59		metrics = $(this).data('metrics'),
60		now = new Date().getTime();
61	if (!metrics || metrics.expire < now ) {
62		metrics = {
63			expire: now + METRICS_EXPIRE_MS,
64			offset:$elm.offset(),
65			height:$elm.outerHeight()
66		};
67		$elm.data('metrics', metrics);
68	}
69	
70	if (!metrics.offset || !metrics.height) {
71		return false;
72	}
73	
74	var et = metrics.offset.top, eb = et + metrics.height;
75	var ret = et < ST + VH + offset && eb > ST + offset && $elm.is(':visible');
76	
77	// parents scroll
78	
79	// check overflowed in parents
80	if (ret) {
81		var $p = $elm.parentsUntil('body').filter(function(){
82				return this.clientHeight != this.scrollHeight
83						&& $(this).css('overflow') != 'visible';
84			})
85			.each(function(){
86				
87				// DEBUG && console.log('isInView:checking hidden parent', this);
88				
89				var $c = $(this),
90					metrics = $c.data('metrics'),
91					now = new Date().getTime();
92				
93				if (!metrics || metrics.expire < now ) {
94					metrics = {
95						expire: now + METRICS_EXPIRE_MS,
96						offset:$c.offset(),
97						height:$c.outerHeight()
98					};
99					$c.data('metrics', metrics);
100				}
101				
102				var pt = metrics.offset.top,
103					pb = pt + metrics.height;
104				
105				if (et > pb || eb < pt ){
106					// hidden by parent scroll
107					
108					// DEBUG && console.log('isInView:hidden by parent', this);
109					
110					ret = false;
111					return false;
112				}
113			});
114	}
115	
116	// DEBUG && console.log('isInView:' + ret, $elm);
117	
118	return ret;
119}
120
121var WATCHING_SELECTORS = [];
122var WATCH_INTERVAL = 1000/30; // 30fps
123
124var WATCHING_SELECTOR = '';
125var $WATCHING_ELEMENTS = $();
126
127$.registerOnScrolledIntoView = function(target){
128	if (typeof target == 'string') {
129		if ($.inArray(target, WATCHING_SELECTORS) == -1){
130			WATCHING_SELECTORS.push(target);
131			WATCHING_SELECTOR = WATCHING_SELECTORS.join(',');
132		}
133	} else {
134		$WATCHING_ELEMENTS.add($(target));
135		$(target).on('remove DOMNodeRemoved', function(ev){
136			$.unregisterOnScrolledIntoView(ev.target);
137		});
138	}
139	checkInView();
140};
141
142$.unregisterOnScrolledIntoView = function(target){
143	if (typeof target == 'string') {
144		var idx = $.inArray(target, WATCHING_SELECTORS);
145		if (idx == -1){
146			WATCHING_SELECTORS.splice(idx, 1);
147			WATCHING_SELECTOR = WATCHING_SELECTORS.join(',');
148		}
149	} else {
150		target = $(target);
151		$WATCHING_ELEMENTS = $WATCHING_ELEMENTS.filter(function(){
152			return !target.is(this);
153		});
154	}
155	checkInView();
156};
157
158if (window.IntersectionObserver && window.MutationObserver) {
159	function isTarget(node) {
160		return (WATCHING_SELECTOR && node.matches(WATCHING_SELECTOR))
161			|| !!($WATCHING_ELEMENTS.filter(function(){return $(this).is(node)})[0]);
162	}
163	var intersection_observer = new IntersectionObserver(function(entries){
164		entries.forEach(function(entry){
165			if (entry.isIntersecting) {
166				DEBUG && console.log('onscrolledintoview:scrolled into view', entry.target);
167				$(entry.target).trigger('scrolledintoview');
168			} else {
169				DEBUG && console.log('onscrolledintoview:scrolled outof view', entry.target);
170				$(entry.target).trigger('scrolledoutofview');
171			}
172		})
173	}, {
174		root: null,
175		rootMargin: "0px",
176		threshold: 0,
177	});
178	var mutation_observer = new MutationObserver(function(mutations){
179		mutations.forEach(function(mutation){
180			mutation.addedNodes.forEach(function(node){
181				if (node.nodeType == node.ELEMENT_NODE) {
182					if (isTarget(node)) {
183						DEBUG && console.log('onscrolledintoview:io.observe', node)
184						intersection_observer.observe(node);
185					} else {
186						$(node).find(WATCHING_SELECTOR).each(function(){
187							DEBUG && console.log('onscrolledintoview:io.observe', this)
188							intersection_observer.observe(this);
189						});
190					}
191				}
192			});
193			mutation.removedNodes.forEach(function(node){
194				if (node.nodeType == node.ELEMENT_NODE) {
195					if (isTarget(node)) {
196						DEBUG && console.log('onscrolledintoview:io.unobserve', node)
197						intersection_observer.unobserve(node);
198					} else {
199						$(node).find(WATCHING_SELECTOR).each(function(){
200							DEBUG && console.log('onscrolledintoview:io.unobserve', this)
201							intersection_observer.unobserve(this);
202						});
203					}
204				}
205			});
206		})
207	});
208	$(function(){
209		mutation_observer.observe(document.body, {
210			childList:true,
211			subtree:true,
212		});
213	});
214	$.registerOnScrolledIntoView = function(target){
215		DEBUG && console.log('onscrolledintoview:register:' + target);
216		if (typeof target == 'string') {
217			if ($.inArray(target, WATCHING_SELECTORS) == -1){
218				WATCHING_SELECTORS.push(target);
219				WATCHING_SELECTOR = WATCHING_SELECTORS.join(',');
220				DEBUG && console.log('onscrolledintoview:WATCHING_SELECTOR=' + WATCHING_SELECTOR);
221			}
222			$(target).each(function(){
223				DEBUG && console.log('onscrolledintoview:io.observe', this)
224				intersection_observer.observe(this);
225			});
226		} else {
227			$WATCHING_ELEMENTS.add($(target));
228			$(target).on('remove DOMNodeRemoved', function(ev){
229				$.unregisterOnScrolledIntoView(ev.target);
230			});
231			$(target).each(function(){
232				DEBUG && console.log('onscrolledintoview:io.observe', this);
233				intersection_observer.observe(this);
234			})
235		}
236		checkInView();
237	};
238	
239	$.unregisterOnScrolledIntoView = function(target){
240		DEBUG && console.log('onscrolledintoview:unregister:' + target);
241		if (typeof target == 'string') {
242			var idx = $.inArray(target, WATCHING_SELECTORS);
243			if (idx == -1){
244				WATCHING_SELECTORS.splice(idx, 1);
245				WATCHING_SELECTOR = WATCHING_SELECTORS.join(',');
246				DEBUG && console.log('onscrolledintoview:WATCHING_SELECTOR=' + WATCHING_SELECTOR);
247			}
248			$(target).each(function(){
249				DEBUG && console.log('onscrolledintoview:io.unobserve', this);
250				intersection_observer.unobserve(this);
251			});
252		} else {
253			target = $(target);
254			$WATCHING_ELEMENTS = $WATCHING_ELEMENTS.filter(function(){
255				return !target.is(this);
256			});
257			$(target).each(function(){
258				DEBUG && console.log('onscrolledintoview:io.unobserve', this);
259				intersection_observer.unobserve(this);
260			});
261		}
262		checkInView();
263	};
264	return;
265}
266
267
268// スクロール、リサイズ中かどうかを検知
269var ONSCROLL_MS = 1000/33; // 33fps
270
271var scrolling = false, _t;
272$(window).on('scroll resize', function(){
273	clearTimeout(_t);
274	scrolling = true;
275	_t = setTimeout(function(){
276		scrolling = false;
277	}, ONSCROLL_MS);
278});
279
280// スクロール、リサイズ中の場合のみ、viewに入っているかチェック
281var REFRESH_INTERVAL = 1000/4; // 4fps
282var _REFRESH_CNT = Math.floor(REFRESH_INTERVAL / ONSCROLL_MS);
283
284var _refresh = _REFRESH_CNT;
285setInterval(function(){
286	if (scrolling || _refresh == 0) {
287		checkInView();
288	}
289	_refresh--;
290	if (_refresh < 0) {
291		_refresh = _REFRESH_CNT;
292	}
293}, ONSCROLL_MS);
294
295function checkInView() {
296	var $target = $(WATCHING_SELECTOR).add($WATCHING_ELEMENTS);
297	$target.each(function(){
298		var $elm = $(this), in_view_old = $elm.data('in_view') || {};
299		
300		// 1画面上、ジャスト、1画面下も判定
301		var in_view = {
302			above:$elm.isInView(-VH),
303			just:$elm.isInView(),
304			below:$elm.isInView(VH)
305		};
306		
307		if (!in_view_old.just && in_view.just ) {
308			DEBUG && console.log('scrolledintoview', $elm);
309			$elm.trigger('scrolledintoview');
310		} else if (in_view_old.just && !in_view.just) {
311			DEBUG && console.log('scrolledoutofview', $elm);
312			$elm.trigger('scrolledoutofview');
313		}
314		if (SCROLLING_DOWN) {
315			// スクロールダウン時に1画面下に入った
316			if (!in_view_old.below && in_view.below) {
317				DEBUG && console.log('scrollingintoview:below', $elm);
318				$elm.trigger('scrollingintoview');
319			}
320		} else if (SCROLLING_UP) {
321			// スクロールアップ時に1画面上に入った
322			if (!in_view_old.above && in_view.above) {
323				DEBUG && console.log('scrollingintoview:above', $elm);
324				$elm.trigger('scrollingintoview');
325			}
326		}
327		
328		$elm.data('in_view', in_view);
329	});
330}
331
332})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.