PageSourceSearch

https://mypage.otsuka-shokai.co.jp/common-o/js/vendor/jquery.menu-aim.js

js otsuka-shokai.co.jp collected 2026-09-24 08:33:40 UTC 12,910 bytes, 378 lines download raw bytes

vendor: 5,620 bytes, lines 1-180
1/**
2 * jQuery.menuAim
3 * キーボード操作できるようにカスタマイズ済み(JIS X 8341-3 に対応)
4 * menu-aim is a jQuery plugin for dropdown menus that can differentiate
5 * between a user trying hover over a dropdown item vs trying to navigate into
6 * a submenu's contents.
7 *
8 * menu-aim assumes that you have are using a menu with submenus that expand
9 * to the menu's right. It will fire events when the user's mouse enters a new
10 * dropdown item *and* when that item is being intentionally hovered over.
11 *
12 * __________________________
13 * | Monkeys  >|   Gorilla  |
14 * | Gorillas >|   Content  |
15 * | Chimps   >|   Here	 |
16 * |___________|____________|
17 *
18 * In the above example, "Gorillas" is selected and its submenu content is
19 * being shown on the right. Imagine that the user's cursor is hovering over
20 * "Gorillas." When they move their mouse into the "Gorilla Content" area, they
21 * may briefly hover over "Chimps." This shouldn't close the "Gorilla Content"
22 * area.
23 *
24 * This problem is normally solved using timeouts and delays. menu-aim tries to
25 * solve this by detecting the direction of the user's mouse movement. This can
26 * make for quicker transitions when navigating up and down the menu. The
27 * experience is hopefully similar to amazon.com/'s "Shop by Department"
28 * dropdown.
29 *
30 * Use like so:
31 *
32 *	  $("#menu").menuAim({
33 *		  activate: $.noop,  // fired on row activation
34 *		  deactivate: $.noop  // fired on row deactivation
35 *	  });
36 *
37 *  ...to receive events when a menu's row has been purposefully (de)activated.
38 *
39 * The following options can be passed to menuAim. All functions execute with
40 * the relevant row's HTML element as the execution context ('this'):
41 *
42 *	  .menuAim({
43 *		  // Function to call when a row is purposefully activated. Use this
44 *		  // to show a submenu's content for the activated row.
45 *		  activate: function() {},
46 *
47 *		  // Function to call when a row is deactivated.
48 *		  deactivate: function() {},
49 *
50 *		  // Function to call when mouse enters a menu row. Entering a row
51 *		  // does not mean the row has been activated, as the user may be
52 *		  // mousing over to a submenu.
53 *		  enter: function() {},
54 *
55 *		  // Function to call when mouse exits a menu row.
56 *		  exit: function() {},
57 *
58 *		  // Selector for identifying which elements in the menu are rows
59 *		  // that can trigger the above events. Defaults to "> li".
60 *		  rowSelector: "> li",
61 *
62 *		  // You may have some menu rows that aren't submenus and therefore
63 *		  // shouldn't ever need to "activate." If so, filter submenu rows w/
64 *		  // this selector. Defaults to "*" (all elements).
65 *		  submenuSelector: "*",
66 *
67 *		  // Direction the submenu opens relative to the main menu. Can be
68 *		  // left, right, above, or below. Defaults to "right".
69 *		  submenuDirection: "right"
70 *	  });
71 *
72 * https://github.com/kamens/jQuery-menu-aim
73*/
74(function($) {
75
76	$.fn.menuAim = function(opts) {
77		// Initialize menu-aim for all elements in jQuery collection
78		this.each(function() {
79			init.call(this, opts);
80		});
81
82		return this;
83	};
84
85	function init(opts) {
86		var $menu = $(this),
87			activeRow = null,
88			mouseLocs = [],
89			lastDelayLoc = null,
90			timeoutId = null,
91			options = $.extend({
92				rowSelector: "> ul > li",
93				rowSelectorKeyboard: "> ul > li > a", /* アクセシビリティのため、キーボード操作に対応 */
94				submenuSelector: "*",
95				submenuDirection: "below",
96				tolerance: 75,  // bigger = more forgivey when entering submenu
97				enter: $.noop,
98				exit: $.noop,
99				activate: $.noop,
100				deactivate: $.noop,
101				exitMenu: $.noop
102			}, opts);
103
104		var MOUSE_LOCS_TRACKED = 3,  // number of past mouse locations to track
105			DELAY = 300;  // ms delay when user appears to be entering submenu
106
107		/**
108		 * Keep track of the last few locations of the mouse.
109		 */
110		var mousemoveDocument = function(e) {
111				mouseLocs.push({x: e.pageX, y: e.pageY});
112
113				if (mouseLocs.length > MOUSE_LOCS_TRACKED) {
114					mouseLocs.shift();
115				}
116			};
117
118		/**
119		 * Cancel possible row activations when leaving the menu entirely
120		 */
121		var mouseleaveMenu = function() {
122				if (timeoutId) {
123					clearTimeout(timeoutId);
124				}
125
126				// If exitMenu is supplied and returns true, deactivate the
127				// currently active row on menu exit.
128				if (options.exitMenu(this)) {
129					if (activeRow) {
130						options.deactivate(activeRow);
131					}
132
133					activeRow = null;
134				}
135			};
136
137		/**
138		 * Trigger a possible row activation whenever entering a new row.
139		 */
140		var mouseenterRow = function() {
141				if (timeoutId) {
142					// Cancel any previous activation delays
143					clearTimeout(timeoutId);
144				}
145
146				options.enter(this);
147				possiblyActivate(this);
148			},
149			mouseleaveRow = function() {
150				options.exit(this);
151				if (!activationDelay) {
152					options.deactivate(this);
153				}
154			};
155
156		/*
157		 * Immediately activate a row if the user clicks on it.
158		var clickRow = function() {
159				activate(this);
160			};
161		 */
162		/**
163		 * Activate a menu row.
164		 */
165		var activate = function(row) {
166				if (row == activeRow || activeRow) {
167					options.deactivate(activeRow);
168				}
169				
170
171				options.activate(row);
172				activeRow = row;
173			};
174
175		/**
176		 * Possibly activate a menu row. If mouse movement indicates that we
177		 * shouldn't activate yet because user may be trying to enter
178		 * a submenu's content, then delay and check again later.
179		 */
180		var possiblyActivate = function(row) {
181				var delay = activationDelay();
182				if (delay) {
183					timeoutId = setTimeout(function() {
184						possiblyActivate(row);
185					}, delay);
186				} else {
187					activate(row);
188				}
189			};
190
191		/**
192		 * Return the amount of time that should be used as a delay before the
193		 * currently hovered row is activated.
194		 *
195		 * Returns 0 if the activation should happen immediately. Otherwise,
196		 * returns the number of milliseconds that should be delayed before
197		 * checking again to see if the row should be activated.
198		 */
199		var activationDelay = function() {
200				if (!activeRow || !$(activeRow).is(options.submenuSelector)) {
201					// If there is no other submenu row already active, then
202					// go ahead and activate immediately.
203					return 0;
204				}
205
206				var offset = $menu.offset(),
207					upperLeft = {
208						x: offset.left,
209						y: offset.top - options.tolerance
210					},
211					upperRight = {
212						x: offset.left + $menu.outerWidth(),
213						y: upperLeft.y
214					},
215					lowerLeft = {
216						x: offset.left,
217						y: offset.top + $menu.outerHeight() + options.tolerance
218					},
219					lowerRight = {
220						x: offset.left + $menu.outerWidth(),
221						y: lowerLeft.y
222					},
223					loc = mouseLocs[mouseLocs.length - 1],
224					prevLoc = mouseLocs[0];
225
226				if (!loc) {
227					return 0;
228				}
229
230				if (!prevLoc) {
231					prevLoc = loc;
232				}
233
234				if (prevLoc.x < offset.left || prevLoc.x > lowerRight.x ||
235					prevLoc.y < offset.top || prevLoc.y > lowerRight.y) {
236					// If the previous mouse location was outside of the entire
237					// menu's bounds, immediately activate.
238					return 0;
239				}
240
241				if (lastDelayLoc &&
242						loc.x == lastDelayLoc.x && loc.y == lastDelayLoc.y) {
243					// If the mouse hasn't moved since the last time we checked
244					// for activation status, immediately activate.
245					return 0;
246				}
247
248				// Detect if the user is moving towards the currently activated
249				// submenu.
250				//
251				// If the mouse is heading relatively clearly towards
252				// the submenu's content, we should wait and give the user more
253				// time before activating a new row. If the mouse is heading
254				// elsewhere, we can immediately activate a new row.
255				//
256				// We detect this by calculating the slope formed between the
257				// current mouse location and the upper/lower right points of
258				// the menu. We do the same for the previous mouse location.
259				// If the current mouse location's slopes are
260				// increasing/decreasing appropriately compared to the
261				// previous's, we know the user is moving toward the submenu.
262				//
263				// Note that since the y-axis increases as the cursor moves
264				// down the screen, we are looking for the slope between the
265				// cursor and the upper right corner to decrease over time, not
266				// increase (somewhat counterintuitively).
267				function slope(a, b) {
268					return (b.y - a.y) / (b.x - a.x);
269				};
270
271				var decreasingCorner = upperRight,
272					increasingCorner = lowerRight;
273
274				// Our expectations for decreasing or increasing slope values
275				// depends on which direction the submenu opens relative to the
276				// main menu. By default, if the menu opens on the right, we
277				// expect the slope between the cursor and the upper right
278				// corner to decrease over time, as explained above. If the
279				// submenu opens in a different direction, we change our slope
280				// expectations.
281				if (options.submenuDirection == "left") {
282					decreasingCorner = lowerLeft;
283					increasingCorner = upperLeft;
284				} else if (options.submenuDirection == "below") {
285					decreasingCorner = lowerRight;
286					increasingCorner = lowerLeft;
287				} else if (options.submenuDirection == "above") {
288					decreasingCorner = upperLeft;
289					increasingCorner = upperRight;
290				}
291
292				var decreasingSlope = slope(loc, decreasingCorner),
293					increasingSlope = slope(loc, increasingCorner),
294					prevDecreasingSlope = slope(prevLoc, decreasingCorner),
295					prevIncreasingSlope = slope(prevLoc, increasingCorner);
296
297				if (decreasingSlope < prevDecreasingSlope &&
298						increasingSlope > prevIncreasingSlope) {
299					// Mouse is moving from previous location towards the
300					// currently activated submenu. Delay before activating a
301					// new menu row, because user may be moving into submenu.
302					lastDelayLoc = loc;
303					return DELAY;
304				}
305
306				lastDelayLoc = null;
307				return 0;
308			};
309
310		/**
311		 * Hook up initial menu events
312		 */
313		$menu
314			.mouseleave(mouseleaveMenu)
315			.find(options.rowSelector)
316				.mouseenter(mouseenterRow)
317				.mouseleave(mouseleaveRow);
318//				.click(clickRow);
319
320		/**
321		 * アクセシビリティのため、キーボード操作に対応する
322		 */
323		$menu
324			.find( options.rowSelectorKeyboard )
325				.focus( mouseenterRow );
326		/**
327		 * menu-aim は、キーボード操作で rowSelector 領域から出たときにイベントが発生しない
328		 * そのため、ナビの端までキーボードでいくと、ドロップダウンメニューが閉じないのを解決する
329		 */
330		var keyboardFocusLeave = function( event ){ /* フォーカス移動後の状態は keyup でないと取得できない */
331			if( event.keyCode === 9 ){ /* Tab */
332				if( $(activeRow).closest('li').find( event.target ).length === 0 ){
333					options.deactivate(activeRow);
334					activeRow = null;
335				}
336			}
337		};
338		$( document ).on( 'keyup', keyboardFocusLeave );
339
340
341
342		$(document).mousemove(mousemoveDocument);
343
344	};
345})(jQuery);
346
347
348$(function(){
349
350	if( ( document.OBSJL.utility.getDevice !== 'tab' ) && ( document.OBSJL.utility.getDevice !== 'sp' ) ){ /* 'sp' はスマホから「PC版を表示」したときのため */
351
352		/*
353		 * アクセシビリティのため、キーボードでも動作するように設定する:
354		 * rowSelector と rowSelectorKeyboard: マウスはブロック要素で起動するが、キーボードはキーボードフォーカスが当たる要素でないと起動できない。両方で動くようにする。
355		 * activate: マウスとキーボードでDOMの起点が異なるので、closest()であつかう。(以下
355同じ)
356		 * deactivate: deactivateは、次または前のメニューにキーボードフォーカスが移ったときに起動する。
357		 * exit: exitはキーボード操作では発生しない。逆にいうと、キーボード操作で出したメニューはdeactivateでないと閉じない。
358		 */
359
360		$('#header-global-nav').menuAim({
361			rowSelector: '.global-li',
362			rowSelectorKeyboard : '.global-li > .static > a',
363			activate: function(row){ $(row).closest('.global-li').find('.dropdown').show(); },
364			deactivate: function(row){ $(row).closest('.global-li').find('.dropdown').hide(); },
365			exit: function(row){ $(row).closest('.global-li').find('.dropdown').hide(); }
366		});
367	
368		$('#header-local-nav').menuAim({
369			rowSelector: '.local-li',
370			rowSelectorKeyboard : '.local-li > .static > a',
371			activate: function(row){ $(row).closest('.local-li').find('.dropdown').show(); },
372			deactivate: function(row){ $(row).closest('.local-li').find('.dropdown').hide(); },
373			exit: function(row){ $(row).closest('.local-li').find('.dropdown').hide(); }
374		});
375
376	}
377
378});

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.