Tab Focus through Google Search Results

Use the tab key to navigate through Google and DuckDuckGo search results

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        Tab Focus through Google Search Results
// @description Use the tab key to navigate through Google and DuckDuckGo search results
// @version     1.1.3
// @match       *://*/search*
// @include     *://*.google.*/search*
// @match       *://*.duckduckgo.com/*
// @grant       none
// @author      szupie [email protected]
// @namespace   szupie
// ==/UserScript==
(function () {
'use strict';

let selectors;

function init() {
	const results = document.querySelectorAll(selectors['resultTitle']);

	for (let i=0; i<results.length; i++) {
		const linkNode = results[i].closest('a');
		linkNode.setAttribute('tabindex', 1);
	}

	// capture focus changes on results list to scroll entire result into view
	document.querySelector(selectors['resultsDiv']).addEventListener("focus", e => {
		// only perform scroll if newly focused element is result link
		if (e.target.getAttribute('tabindex') === '1') {
			const resultNode = e.target.closest(selectors['resultNode']);
			const bounds = resultNode.getBoundingClientRect();
			// scroll item to top if it extends past viewport top,
			// or to bottom if it extends past viewport bottom
			if (bounds.top < 0) {
				resultNode.scrollIntoView();
			} else if (bounds.bottom > window.innerHeight) {
				resultNode.scrollIntoView(false);
			}
		}
	}, true);

	const styleNode = document.createElement('style');
	styleNode.type = 'text/css';
	styleNode.innerHTML = css;
	styleNode.id = 'tab-focus-results';
	document.getElementsByTagName('head')[0].appendChild(styleNode);
}

// CSS selectors
const googleSelectors = {
	'resultTitle': '.LC20lb',
	'resultsDiv': '#res',
	'resultNode': '.g'
};

const ddgSelectors = {
	'resultTitle': '#links a.result__a:not(.result__sitelink-title)',
	'resultsDiv': '#links',
	'resultNode': '.result'
};

if (window.location.hostname != 'duckduckgo.com') {
	selectors = googleSelectors;
} else {
	selectors = ddgSelectors;
}

// Style to indicate focus
const css =
`a[tabindex="1"]:focus::after {
	content: '►';
	position: absolute;
	right: 100%;
	margin-top: 1em;
	margin-right: 2px;
	color: #4273DB;
	font: 11px arial, sans-serif;
}
a[tabindex="1"]:focus h3 {
	outline: 2px solid;
}
a[tabindex="1"]:focus {
	outline: none;
}

/* for duckduckgo */
a.result__a[tabindex="1"]:focus::after {
	margin-top: 0.25em;
	margin-right: 0;
}`;

if (document.querySelector(selectors['resultTitle'])) {
	init();
} else {
	window.addEventListener('load', init);
}

})();