Mighty Mouse の調子が悪いので Greasemonkey
MacBook で使っている Mighty Mouse のスクロールボールの調子が悪い。
掃除(http://support.apple.com/kb/HT1537?viewlocale=ja_JP&locale=ja_JP) をしてみてもよくならない。このままではWEBサイトを見ているときに手軽にスクロールできなくて困るので、Greasemonkey でスクロールできるようにしてみた。
動作のイメージとしては Wii のブラウザのように、つまんでスクロールさせるみたいな感じ。
使い方 1 任意の場所で長めのクリック 2 マウスカーソルを動かすとその方向へスクロールする 3 もう一回クリックするとスクロール解除
意外と便利。スクロール問題は解決した。
wiiscroll.user.js
// ==UserScript== // @name WiiScroll // @namespace http://d.hatena.ne.jp/mohayonao // @include * // ==/UserScript== (function() { // 状態遷移を管理する function Observer() { this.initialize.apply(this, arguments); }; Observer.prototype = { initialize: function(name) { this.name = name; }, setup: function(config) { this.eventListener = new EventListenerList(config.eventListener, this); this.onStart = config.onStart || function() {}; this.onStop = config.onStop || function() {}; }, start: function(event) { this.onStart(this, event); this.eventListener.start(); }, stop: function(event) { this.eventListener.stop(); this.onStop(this, event); }, move: function(next, event) { this.stop(event); next.start(event); } }; // イベントリスナー管理用 function EventListenerList() { this.initialize.apply(this, arguments); } EventListenerList.prototype = { initialize: function(list, observer) { this.list = []; if (!list) return; for (var type in list) { (function (self, listener) { var curriedListener = function(event) { return listener(observer, event); }; self.list.push({type: type, listener: curriedListener}); })(this, list[type]); } }, start: function() { for(var i = 0; i < this.list.length; i++) { var t = this.list[i]; document.addEventListener(t.type, t.listener, false); } }, stop: function() { for(var i = 0; i < this.list.length; i++) { var t = this.list[i]; document.removeEventListener(t.type, t.listener, false); } } }; function Arrow() { this.initialize.apply(this, arguments); } Arrow.prototype = { initialize: function(x, y) { this.baseX = this.curX = x; this.baseY = this.curY = y; this.distanceX = 0; this.distanceY = 0; }, setPosition: function(x, y) { this.curX = x; this.curY = y; this.distanceX = this.baseX - this.curX; this.distanceY = this.baseY - this.curY; }, draw: function(g) { g.strokeStyle = '#66f'; g.fillStyle = '#66f'; g.lineWidth = 2; g.beginPath(); g.arc(this.baseX, this.baseY, 3, 0, Math.PI * 2, false); g.arc(this.curX, this.curY, 3, 0, Math.PI * 2, false); g.fill(); g.closePath(); g.beginPath(); g.moveTo(this.baseX, this.baseY); g.lineTo(this.curX, this.curY); g.stroke(); g.closePath(); } } function Canvas() { this.initialize.apply(this, arguments); } Canvas.prototype = { initialize: function() { this.canvas = document.createElement('canvas'); this.canvas.style.position = "fixed"; this.canvas.style.left = 0; this.canvas.style.top = 0; this.canvas.style.zIndex = 100; this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight; document.body.appendChild(this.canvas); this.context = this.canvas.getContext('2d'); }, quit: function() { document.body.removeChild(this.canvas); delete this; }, clear: function() { this.context.clearRect(0, 0, window.innerWidth, window.innerHeight); }, draw: function(obj) { if (typeof(obj) == 'object' && !!obj.draw) { obj.draw(this.context); } } }; function getScrollableElement(elem) { if (!elem.tagName) return; var overflow = getComputedStyle(elem, '').overflow; if (overflow == 'scroll' || overflow == 'auto') { if (elem.scrollHeight != elem.offsetHeight || elem.scrollWidth != elem.offsetWidth ) { return elem; } } return arguments.callee(elem.parentNode); } var init = new Observer('init'); var wait = new Observer('wait'); var standby = new Observer('standby'); var monitor = new Observer('monitor'); init.setup({ eventListener: { mousedown: function(self, event) { self.move(wait, event); } } }); wait.setup({ eventListener: { mouseup: function(self, event) { self.move(init, event); }, mousemove: function(self, event) { self.move(init, event); } }, onStart: function(self, event) { self.timerID = setTimeout(function() { self.move(standby, event); }, 150) }, onStop: function(self, event) { if (self.timerID > 0) { clearTimeout(self.timerID); } } }); standby.setup({ eventListener: { mouseup : function(self, event) { self.move(monitor, event); }, mousemove: function(self, event) { self.move(init, event); } } }); monitor.setup({ eventListener: { mousedown: function(self, event) { self.move(init, event); }, mousemove: function(self, event) { self.arrow.setPosition(event.clientX, event.clientY); self.canvas.clear(); self.canvas.draw(self.arrow); } }, onStart: function(self, event) { self.canvas = new Canvas(); self.arrow = new Arrow(event.clientX, event.clientY); self.canvas.draw(self.arrow); var target = getScrollableElement(event.target); self.timerID = setInterval(function() { var scrollOffsetX = self.arrow.distanceX / 2; var scrollOffsetY = self.arrow.distanceY / 5; if (!!target) { target.scrollLeft -= scrollOffsetX; target.scrollTop -= scrollOffsetY; } else { scrollBy(-scrollOffsetX, -scrollOffsetY); } }, 150); }, onStop: function(self, event) { if (self.timerID > 0) { clearInterval(self.timerID); } self.arrow = null; self.canvas.quit(); } }); init.start(); })();