Safari で navigate する
Safari で Opera のような navigate をしたかったので書きました。(http://d.hatena.ne.jp/mzp/20081121 に触発されて)
それから、id:hiru926 が navigate の次の行き先を表示できれば使いやすいのに、と言っていたので適当に実装してみました。shift キーを押している間だけ変なフレームがでます。
現状では先読みが見にくかったり navigate がやっぱり微妙に変だったりするので、まだいろいろと改善の余地ありかなと。
// ==UserScript== // @name navigate.js // @namespace http://d.hatena.ne.jp/zyxwv/ // @include * // ==/UserScript== (function (){ const minimumWidth = 100; const minimumHeight = 60; const borderWidth = 3; var d = document; var xlinks = []; var ylinks = []; var currentTarget; var frame; var subframes = []; function prepareLinks() { function collectTags(tagname) { var as = d.getElementsByTagName(tagname); var counter = xlinks.length; for (var i=0; i < as.length ; i++) { if( tagname != 'a' || as[i].hasAttribute('href') ) { var xy = Position.cumulativeOffset(as[i]); var xlow, xhigh, ylow, yhigh; if ( as[i].offsetHeight < minimumHeight ) { ylow = xy[1] + as[i].offsetHeight/2 - minimumHeight/2; yhigh = xy[1] + as[i].offsetHeight/2 + minimumHeight/2; } else { ylow = xy[1]; yhigh = xy[1] + as[i].offsetHeight; } if ( as[i].offsetWidth < minimumWidth ) { xlow = xy[0] + as[i].offsetWidth/2 - minimumWidth/2; xhigh = xy[0] + as[i].offsetWidth/2 + minimumWidth/2; } else { xlow = xy[0]; xhigh = xy[0] + as[i].offsetWidth; } xlinks[counter] = {dom:as[i], index:xy[0], low:ylow, high:yhigh}; ylinks[counter] = {dom:as[i], index:xy[1], low:xlow, high:xhigh}; counter++; } } } collectTags('a'); collectTags('input'); collectTags('object'); xlinks.sort(function (a,b){ return a.index - b.index }); ylinks.sort(function (a,b){ return a.index - b.index }); } function isInWindow(dom){ xy = Position.cumulativeOffset(dom); return !(xy[1] > window.pageYOffset + window.innerHeight || xy[1] + dom.offsetHeight < window.pageYOffset || xy[0] > window.pageXOffset + window.innerWidth || xy[0] + dom.offsetWidth < window.pageXOffset) } function setupForcus(dom) { dom.focus(); if (!isInWindow(dom)) { var pos = Position.cumulativeOffset(dom)[1]-window.innerHeight/2; window.scrollTo(0, pos); } } function findIndexOfTarget(list) { var pos = 0; while (list[pos].dom != currentTarget.dom) { pos++; } return pos; } function setCurrentTarget(target) { function setFrame(dom){ xy = Position.cumulativeOffset(dom); frame.style.display="block"; frame.style.width=(dom.offsetWidth+8)+"px"; frame.style.height=(dom.offsetHeight+8)+"px"; frame.style.top=xy[1]-4-borderWidth+"px"; frame.style.left=xy[0]-4-borderWidth+"px"; } setupForcus(target.dom); setFrame(target.dom); currentTarget = target; setSubTarget(); } function setSubTarget() { function setSubFrame(i){ return function(target) { dom = target.dom; xy = Position.cumulativeOffset(dom); subframes[i].style.display="block"; subframes[i].style.width=(dom.offsetWidth+8)+"px"; subframes[i].style.height=(dom.offsetHeight+8)+"px"; subframes[i].style.top=xy[1]-4-borderWidth+"px"; subframes[i].style.left=xy[0]-4-borderWidth+"px"; } } pos = findIndexOfTarget(ylinks); findNextNode(1, pos, ylinks.length, ylinks, currentTarget.dom.offsetHeight, setSubFrame(0)); // down findNextNode(-1, pos, ylinks.length, ylinks, currentTarget.dom.offsetHeight, setSubFrame(1)); // up pos = findIndexOfTarget(xlinks); findNextNode(1, pos, xlinks.length, xlinks, currentTarget.dom.offsetWidth, setSubFrame(2)); // right findNextNode(-1, pos, xlinks.length, xlinks, currentTarget.dom.offsetWidth, setSubFrame(3)); // left } function findNextNode(direction, start, limit, list, minVariation, whenFound){ whenFound = whenFound || setCurrentTarget; // setup current-target data currentTarget = list[findIndexOfTarget(list)]; for (var pos=start; 0 < pos && pos < limit; pos+=direction) { if (currentTarget.low < list[pos].high && list[pos].low < currentTarget.high && (Math.abs(list[pos].index-currentTarget.index) >= minVariation)) { // TODO: it should search some more candidates whenFound(list[pos]); return; } } // default whenFound(list[pos]); return; } function displaySubFrames() { for (var i=0;i<4;i++) { subframes[i].style.display = 'block'; } } function hideSubFrames() { for (var i=0;i<4;i++) { subframes[i].style.display = 'none'; } } function navigateDOWN() { if (currentTarget == undefined) { setupForcus(ylinks[0].dom); currentTarget = ylinks[0]; } else { var direction = 1; var pos = findIndexOfTarget(ylinks) + direction; findNextNode(direction, pos, ylinks.length, ylinks, currentTarget.dom.offsetHeight); } } function navigateUP() { if (currentTarget == undefined) { setupForcus(ylinks[0].dom); currentTarget = ylinks[0]; } else { var direction = -1; var pos = findIndexOfTarget(ylinks) + direction; findNextNode(direction, pos, ylinks.length, ylinks, currentTarget.dom.offsetHeight); } } function navigateRIGHT() { if (currentTarget == undefined) { setupForcus(xlinks[0].dom); currentTarget = xlinks[0]; } else { var direction = 1; var pos = findIndexOfTarget(xlinks) + direction; findNextNode(direction, pos, xlinks.length, xlinks, currentTarget.dom.offsetWidth); } } function navigateLEFT() { if (currentTarget == undefined) { setupForcus(xlinks[0].dom); currentTarget = xlinks[0]; } else { var direction = -1; var pos = findIndexOfTarget(xlinks) + direction; findNextNode(direction, pos, xlinks.length, xlinks, currentTarget.dom.offsetWidth); } } function setupKeyBinding(){ // handle key input const KeyEvent = { DOM_VK_LEFT: 37, DOM_VK_UP: 38, DOM_VK_RIGHT: 39, DOM_VK_DOWN: 40 }; var isShiftPressed = false; document.addEventListener('keydown', function (e){ var active = d.activeElement; if ( active != undefined && active.tagName == 'INPUT' ) { // no move } else { if( e.shiftKey ) { isShiftPressed = true; displaySubFrames(); switch(e.keyCode) { case KeyEvent.DOM_VK_UP: navigateUP(); e.preventDefault(); break; case KeyEvent.DOM_VK_LEFT: navigateLEFT(); e.preventDefault(); break; case KeyEvent.DOM_VK_RIGHT: navigateRIGHT(); e.preventDefault(); break; case KeyEvent.DOM_VK_DOWN: navigateDOWN(); e.preventDefault(); break; default: break; } } } },true); document.addEventListener('keyup', function(e) { if (isShiftPressed && !e.shiftKey) { hideSubFrames(); } }, true); } function init() { frame = document.createElement('div'); frame.style.border= borderWidth + "px solid royalblue"; frame.style.position="absolute"; frame.style.zindex="255"; document.body.appendChild(frame); for(var i=0;i<4;i++) { subframes[i] = document.createElement('div'); subframes[i].style.border= borderWidth + "px solid yellow"; switch (i) { case 0: // down subframes[i].style.borderTop = (borderWidth*2)+"px solid red" break; case 1: // up subframes[i].style.borderBottom = (borderWidth*2)+"px solid red" break; case 2: // right subframes[i].style.borderLeft = (borderWidth*2)+"px solid red" break; case 3: // left subframes[i].style.borderRight = (borderWidth*2)+"px solid red" break; } subframes[i].style.position="absolute"; subframes[i].style.zindex="255"; document.body.appendChild(subframes[i]); } } // NOW, START! init(); prepareLinks(); setupKeyBinding(); }())