Per-page X-Mouse-like controls: custom actions for extra buttons, wheel, and hover focus (where possible) in Safari.
// ==UserScript==
// @name cool thingy!
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Per-page X-Mouse-like controls: custom actions for extra buttons, wheel, and hover focus (where possible) in Safari.
// @author You
// @match *://*/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
// =========================
// CONFIG: TWEAK THIS PART
// =========================
// Example actions:
// - "back": window.history.back()
// - "forward": window.history.forward()
// - "scrollUp"/"scrollDown": window.scrollBy(...)
// - "top": scroll to top
// - "bottom": scroll to bottom
// - "none": do nothing / let default happen
const CONFIG = {
// Map mouse buttons to actions
// 0 = left, 1 = middle, 2 = right, 3+ = extra (if browser exposes them)
buttonMap: {
1: 'autoScrollToggle', // middle click toggles auto-scroll
3: 'back', // XButton1 (if exposed) -> back
4: 'forward' // XButton2 (if exposed) -> forward
},
// Auto-scroll speed (pixels per frame)
autoScrollSpeed: 8,
// Enable "focus window on hover" style behavior (limited in browser)
focusOnHover: true
};
// =========================
// INTERNAL STATE
// =========================
let autoScrollActive = false;
let autoScrollDirection = 1; // 1 = down, -1 = up
let autoScrollRAF = null;
// =========================
// ACTION HANDLERS
// =========================
function doAction(action, event) {
switch (action) {
case 'back':
event.preventDefault();
window.history.back();
break;
case 'forward':
event.preventDefault();
window.history.forward();
break;
case 'scrollUp':
event.preventDefault();
window.scrollBy({ top: -200, left: 0, behavior: 'smooth' });
break;
case 'scrollDown':
event.preventDefault();
window.scrollBy({ top: 200, left: 0, behavior: 'smooth' });
break;
case 'top':
event.preventDefault();
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
break;
case 'bottom':
event.preventDefault();
window.scrollTo({ top: document.body.scrollHeight, left: 0, behavior: 'smooth' });
break;
case 'autoScrollToggle':
event.preventDefault();
toggleAutoScroll(event);
break;
case 'none':
default:
// Let default behavior happen
break;
}
}
// =========================
// AUTO-SCROLL
// =========================
function autoScrollLoop() {
if (!autoScrollActive) return;
window.scrollBy(0, CONFIG.autoScrollSpeed * autoScrollDirection);
autoScrollRAF = window.requestAnimationFrame(autoScrollLoop);
}
function toggleAutoScroll(event) {
// Direction based on where you clicked relative to center
const centerY = window.innerHeight / 2;
autoScrollDirection = (event.clientY < centerY) ? -1 : 1;
if (autoScrollActive) {
autoScrollActive = false;
if (autoScrollRAF) {
window.cancelAnimationFrame(autoScrollRAF);
autoScrollRAF = null;
}
} else {
autoScrollActive = true;
autoScrollLoop();
}
}
// =========================
// EVENT LISTENERS
// =========================
// Mouse button remapping
window.addEventListener('mousedown', function (e) {
// e.button: 0=left,1=middle,2=right, 3+=extra (if supported)
const action = CONFIG.buttonMap[e.button];
if (!action) return;
doAction(action, e);
}, true);
// Optional: focus-on-hover style (limited)
if (CONFIG.focusOnHover) {
window.addEventListener('mouseover', function (e) {
// We can't change OS-level focus, but we can visually "focus" elements.
const el = e.target;
if (el && el.focus && typeof el.focus === 'function') {
// Only auto-focus inputs, textareas, and contenteditable
const tag = el.tagName.toLowerCase();
const isEditable =
tag === 'input' ||
tag === 'textarea' ||
el.isContentEditable;
if (isEditable) {
el.focus({ preventScroll: true });
}
}
}, true);
}
// Safety: stop auto-scroll on user input
['wheel', 'keydown', 'mousedown'].forEach(type => {
window.addEventListener(type, () => {
if (!autoScrollActive) return;
autoScrollActive = false;
if (autoScrollRAF) {
window.cancelAnimationFrame(autoScrollRAF);
autoScrollRAF = null;
}
}, { passive: true });
});
})();