Skip to content

Instantly share code, notes, and snippets.

@miya2000
Created October 10, 2012 13:04
Show Gist options
  • Save miya2000/3865532 to your computer and use it in GitHub Desktop.
Save miya2000/3865532 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Scrolloo
// @description Yet anther scrollbar and scroll utilities.
// @namespace http://www.hatena.ne.jp/miya2000/scrolloo
// @version 1.00 Beta
// @include http://*
// @exclude http://www.nicovideo.jp/watch/*
// @exclude http://miya2000.hatenablog.com/*
// ==/UserScript==
//*
(function(f) {
//for debug.
return function() {
if (!document.documentElement || !/html/i.test(document.documentElement.nodeName)) return;
var s = document.createElement('script');
s.setAttribute('type', 'text/javascript');
s.setAttribute('style', 'display: none;');
s.textContent = '(' + f.toString() + ')()';
var container = (document.body || document.documentElement);
if (container) container.insertBefore(s, container.firstChild);
};
})
//*/
(function() {
var SCROLLOO_NAMESPACE = 'http://www.hatena.ne.jp/miya2000/scrolloo';
var opera = window.opera;
var Browser = {
Opera: !!window.opera,
Opera_version: window.opera ? parseFloat(window.opera.version()) : null,
Webkit: (navigator.userAgent.indexOf(' AppleWebKit/') >= 0)
};
/// class Smooth - simple smooth animation.
function Smooth(target, property, baseDuration, delay, initialRatio) {
this.target = target;
this.property = property;
this.baseDuration = baseDuration || 180;
this.delay = delay || 16;
this.initialRatio = initialRatio || 0;
}
Smooth.easings = {
easeNone : function easeNone(t, b, c, d) { return c*t/d + b; },
easeInSine : function easeInSine(t, b, c, d) { return -c * Math.cos(t/d *(Math.PI/2)) + c + b; },
easeOutSine : function easeOutSine(t, b, c, d) { return c * Math.sin(t/d *(Math.PI/2)) + b; },
easeInExpo : function easeInExpo(t, b, c, d) { return(t==0) ? b : c * Math.pow(2, 10 *(t/d - 1)) + b - c * 0.001; },
easeOutExpo : function easeOutExpo(t, b, c, d) { return(t==d) ? b+c : c * 1.001 *(-Math.pow(2, -10 * t/d) + 1) + b; }
};
(function(member) { member.apply(Smooth.prototype) })(function() {
this.easing = Smooth.easings.easeOutExpo;
this.duration = function(change, sliding) { return this.baseDuration; };
this.go = function(/*from, */dest) {
var target = this.target, prop = this.property, from;
if (arguments.length > 1) {
from = arguments[0];
dest = arguments[1];
}
else {
from = target[prop];
}
if (this.isActive) {
if (this.dest == dest) return;
this.dest = dest;
this.activity.slide(dest);
return;
}
var begin = parseFloat(from) || 0, end = parseFloat(dest), change = end - begin;
if (this.initialRatio) begin += change * this.initialRatio;
var m = /(?:[+-]?[0-9]*)\.?([0-9]*)(.*)/.exec(dest), scale = (m ? m[1].length : 0), unit = (m ? m[2] : null);
var power = Math.pow(10, scale);
var start = new Date(), duration = this.duration(change, false);
this.from = from; this.dest = dest; this.isActive = true;
this.activity = {
slide: slide,
anim: anim,
dispose: dispose
};
var self = this;
function slide(dest) {
var cur = parseFloat(self.current());
if (!isNaN(cur)) begin = cur;
end = parseFloat(dest);
change = end - begin;
start = new Date();
duration = self.duration(change, true);
}
function anim() {
var finished = false;
try {
var time = new Date() - start, next;
if (time >= duration) {
next = self.dest;
finished = true;
}
else {
next = (self.easing(time, begin, change, duration) * power + 0.5 | 0) / power; // not strict round at negative value.
if (unit) next = next + unit;
}
target[prop] = next;
}
catch(e) { self.clear(); throw e; }
if (finished) {
self.clear();
if (self.onfinish) self.onfinish();
}
else {
if (self.onprogress) self.onprogress();
}
}
var tid = setInterval(anim, this.delay);
function dispose() { if (tid) clearInterval(tid); tid = null; }
if (arguments.length > 1 || duration <= 0) anim(); // call first if "from" set or "duration" isn't set.
};
this.current = function() {
return this.target[this.property];
};
this.clear = function() {
this.isActive = false;
if (this.activity != null) this.activity.dispose();
this.activity = null;
};
this.restore = function() {
if (this.from != null) this.target[this.property] = this.from;
this.clear();
};
});
/// class Scrollable - scrollable element wrapper.
function Scrollable(element) {
if (!(this instanceof Scrollable)) return new Scrollable(element);
this.element = element;
this.scrollElement = Scrollable.fn.getScrollElement(element);
this.scroller = {
'1': new Smooth(this.scrollElement, 'scrollLeft'),
'2': new Smooth(this.scrollElement, 'scrollTop')
};
this.scroller[1].easing = this.scroller[2].easing = Smooth.easings.easeOutSine;
this.scroller[1].duration = this.scroller[2].duration = function(change, sliding) {
var abs = (change ^ (change >> 31)) - (change >> 31); // Math.abs(change);
if (abs < 120) return this.baseDuration * (abs / 120) | 0;
return this.baseDuration;
};
if (this.isRoot() && Browser.Webkit) {
this.scroller[1].onprogress = this.scroller[2].onprogress = function() {
Scrollable.fn.dispatchScrollEvent(document);
};
}
}
Scrollable.HORIZONTAL_AXIS = 1; // compatible MouseScrollEvent(Gecko).
Scrollable.VERTICAL_AXIS = 2;
var Sfn = Scrollable.fn = (function() {
//[[navico notation]]
return {
getRoot: getRoot,
isRoot: isRoot,
dispatchScrollEvent: dispatchScrollEvent,
getClientWidth: getClientWidth,
getClientHeight: getClientHeight,
getScrollWidth: getScrollWidth,
getScrollHeight: getScrollHeight,
getScrollLeft: getScrollLeft,
getScrollTop: getScrollTop,
getScrollElement: getScrollElement,
isScrollable: isScrollable,
isScrollableNode: isScrollableNode,
isScrollableStyle: isScrollableStyle,
scrollDirectTo: scrollDirectTo,
scrollDirectBy: scrollDirectBy,
scrollTo: scrollTo,
scrollBy: scrollBy
};
function getRoot() {
return Scrollable.ROOT || (Scrollable.ROOT = (document.compatMode == 'BackCompat') ? document.body : document.documentElement);
}
function isRoot(ele) {
return ele === getRoot();
}
function dispatchScrollEvent(target) {
var ev = document.createEvent('HTMLEvents');
ev.initEvent('scroll', false, false);
(target || document).dispatchEvent(ev);
}
function getClientWidth(viewElement) {
return viewElement.clientWidth;
}
function getClientHeight(viewElement) {
return viewElement.clientHeight;
}
function getScrollWidth(scrollElement) {
if (Browser.Opera_version < 10.6) {
var style = document.defaultView.getComputedStyle(scrollElement, '');
var pad = 0;
if (Browser.Opera_version <= 10.10) {
pad -= parseInt(style.borderLeftWidth, 10);
}
else {
pad += parseInt(style.paddingLeft, 10);
}
return scrollElement.scrollWidth + pad;
}
else {
return scrollElement.scrollWidth;
}
}
function getScrollHeight(scrollElement) {
if (Browser.Opera_version < 10.6 && !isRoot(scrollElement)) {
var style = document.defaultView.getComputedStyle(scrollElement, '');
var pad = 0;
pad += parseInt(style.paddingTop, 10);
pad -= (scrollElement.offsetHeight - scrollElement.clientHeight - parseInt(style.borderTopWidth, 10) - parseInt(style.borderBottomWidth, 10));
return scrollElement.scrollHeight + pad;
}
else {
return scrollElement.scrollHeight;
}
}
function getScrollLeft(scrollElement) {
return scrollElement.scrollLeft;
}
function getScrollTop(scrollElement) {
return scrollElement.scrollTop;
}
function getScrollElement(ele) {
if (isRoot(ele) && Browser.Webkit) return document.body;
return ele;
}
function isScrollable(ele, h1v2orEither, u1d2orEither) {
var scr = getScrollElement(ele);
if (h1v2orEither == 1) {
if (u1d2orEither == 1) return getScrollLeft(scr) > 0;
if (u1d2orEither == 2) return getScrollLeft(scr) < getScrollWidth(scr) - getClientWidth(ele);
return getScrollWidth(scr) > getClientWidth(ele);
}
if (h1v2orEither == 2) {
if (u1d2orEither == 1) return getScrollTop(scr) > 0;
if (u1d2orEither == 2) return getScrollTop(scr) < getScrollHeight(scr) - getClientHeight(ele);
return getScrollHeight(scr) > getClientHeight(ele);
}
return (getScrollWidth(scr) > getClientWidth(ele)) || (getScrollHeight(scr) > getClientHeight(ele));
}
function isScrollableNode(ele, h1v2) {
if (!ele || ele.nodeType != 1) return false;
if (isRoot(ele)) return true;
if (ele.nodeName == 'TEXTAREA') return true;
return isScrollableStyle(ele, h1v2);
}
function isScrollableStyle(ele, h1v2) {
var style = document.defaultView.getComputedStyle(ele, '');
if (style.display != 'block') return false;
if (typeof style.overflowY == 'undefined') {
return /^(auto|scroll)$/.test(style.overflow);
}
else {
if (h1v2 == 1) return /^(auto|scroll)$/.test(style.overflowX);
if (h1v2 == 2) return /^(auto|scroll)$/.test(style.overflowY);
return /^(auto|scroll)$/.test(style.overflowX) || /^(auto|scroll)$/.test(style.overflowY);
}
}
function scrollDirectTo(ele, h1v2, to) {
var dest = to, max, scr = getScrollElement(ele);
if (h1v2 == 1) max = getScrollWidth(scr) - getClientWidth(ele);
if (h1v2 == 2) max = getScrollHeight(scr) - getClientHeight(ele);
if (dest < 0 ) dest = 0;
if (dest > max) dest = max;
if (h1v2 == 1) scr.scrollLeft = dest;
if (h1v2 == 2) scr.scrollTop = dest;
if (isRoot(ele) && Browser.Webkit) {
dispatchScrollEvent(document);
}
}
function scrollDirectBy(ele, h1v2, delta) {
Sfn.scrollDirectTo(ele, h1v2, (h1v2 == 1 ? getScrollLeft(ele) : getScrollTop(ele)) + delta);
}
function scrollTo(ele, x, y) {
if (x != null) Sfn.scrollDirectTo(ele, 1, x);
if (y != null) Sfn.scrollDirectTo(ele, 2, y);
}
function scrollBy(ele, dx, dy) {
if (dx != null) Sfn.scrollDirectBy(ele, 1, dx);
if (dy != null) Sfn.scrollDirectBy(ele, 2, dy);
}
})();
(function(member) { member.apply(Scrollable.prototype); })(function() {
this.isRoot = function() { return Sfn.isRoot(this.element); };
this.getClientWidth = function() { return Sfn.getClientWidth(this.element); };
this.getClientHeight = function() { return Sfn.getClientHeight(this.element); };
this.getScrollWidth = function() { return Sfn.getScrollWidth(this.scrollElement); };
this.getScrollHeight = function() { return Sfn.getScrollHeight(this.scrollElement); };
this.getScrollLeft = function() { return Sfn.getScrollLeft(this.scrollElement); };
this.getScrollTop = function() { return Sfn.getScrollTop(this.scrollElement); };
this.isScrollable = function(h1v2, u1d2) { return Sfn.isScrollable(this.element, h1v2, u1d2); };
this.isScrollableNode = function(h1v2) { return Sfn.isScrollableNode(this.element, h1v2); };
this.isScrollableStyle = function(h1v2) { return Sfn.isScrollableStyle(this.element, h1v2); };
this.scrollDirectTo = function scrollDirectTo(h1v2, to, smooth) {
if (!smooth) {
Sfn.scrollDirectTo(this.element, h1v2, to);
return;
}
var dest = to, max;
if (h1v2 == 1) max = this.getScrollWidth() - this.getClientWidth();
if (h1v2 == 2) max = this.getScrollHeight() - this.getClientHeight();
if (dest < 0 ) dest = 0;
if (dest > max) dest = max;
this.scroller[h1v2].go(dest);
};
this.scrollDirectBy = function scrollDirectBy(h1v2, delta, smooth) {
var scroller = this.scroller[h1v2];
var from = scroller.isActive ? scroller.dest : scroller.current();
this.scrollDirectTo(h1v2, from + delta, smooth);
};
this.scrollTo = function scrollTo(x, y, smooth) {
if (x != null) this.scrollDirectTo(1, x, smooth);
if (y != null) this.scrollDirectTo(2, y, smooth);
};
this.scrollBy = function scrollTo(dx, dy, smooth) {
if (dx != null) this.scrollDirectBy(1, dx, smooth);
if (dy != null) this.scrollDirectBy(2, dy, smooth);
};
});
// @see Rx.js http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx
var subscribe = (function() {
// avoid global pollution.
var w_ael = window.addEventListener;
var w_rel = window.removeEventListener;
function subscribe(target, type, listener, useCapture) {
useCapture = !!useCapture;
if (target == window) {
w_ael.call(window, type, listener, useCapture);
var disposed = false;
return function() { if (!disposed) w_rel.call(window, type, listener, useCapture); disposed = true; };
}
else {
target.addEventListener(type, listener, useCapture);
var disposed = false;
return function() { if (!disposed) target.removeEventListener(type, listener, useCapture); disposed = true; };
}
};
return subscribe;
})();
function bind(thisobj, func/*, arg1, ...*/) {
if (typeof func != 'function' && typeof thisobj[func] != 'function') {
throw new Error('invalid argument. func:' + func);
}
var pre_args = [];
pre_args.push.apply(pre_args, arguments);
pre_args = pre_args.slice(2);
return function() {
var args = pre_args.concat();
args.push.apply(args, arguments);
var f = (typeof func == 'function') ? func : thisobj[func];
return f.apply(thisobj, args);
};
}
function run(/*f1, ...*/) {
for(var i = 0, len = arguments.length; i < len; i++) {
var f = arguments[i];
if (f) {
if (typeof f == 'function') f.call(this);
else run.apply(this, f); // If f isn't a function, it assume f as an Array.
}
}
}
function runnable(/*f1, ...*/) {
var args = []; args.push.apply(args, arguments);
var self = this;
function runnable_impl() { run.apply(self, args); };
return runnable_impl;
}
function applyProperty(target, prop) {
for (var k in prop) {
if (prop.hasOwnProperty(k)) {
if (k in target) target[k] = prop[k];
}
}
}
function applyStyle(style, prop) {
for (var k in prop) {
if (k in style) {
style[k] = prop[k];
}
else {
var kk = k.charAt(0).toUpperCase() + k.slice(1);
if ( 'O' + kk in style) style['O' + kk] = prop[k];
else if ( 'Moz' + kk in style) style['Moz' + kk] = prop[k];
else if ('Webkit' + kk in style) style['Webkit' + kk] = prop[k];
}
}
}
function addStyle(styleStr) {
var style = document.createElement('style');
style.type = 'text/css';
styleStr = '\n' + styleStr.replace(/(^\s*|\s*$)/, '') + '\n';
if (style.styleSheet) { style.styleSheet.cssText = styleStr; } else { style.textContent = styleStr; }
var container = document.getElementsByTagName('head')[0];
if (!container) container = document.body || document.documentElement;
container.appendChild(style);
return style;
}
function wheelDelta(e) {
if (e.wheelDelta) return e.wheelDelta / 120;
else if (e.detail) return -e.detail / 3;
}
var getContextData = (function() {
var context;
function handler() {
context = null;
}
function getContextData() {
if (context) return context;
setTimeout(handler, 0);
return context = {};
}
return getContextData;
})();
function overridable(obj) {
var base = function() {};
base.prototype = obj;
return new base;
}
function getAbsolutePosition(ele) {
var rect = ele.getBoundingClientRect();
var scr = Sfn.getScrollElement(Sfn.getRoot());
return {
x: rect.left + Sfn.getScrollLeft(scr),
y: rect.top + Sfn.getScrollTop(scr)
};
}
// @see http://d.hatena.ne.jp/amachang/20100624/1277359266
var isElementInDocument = (function() {
function isElementInDocumentA(node) {
return node != null && (node.nodeType == 9 || (node.compareDocumentPosition(node.ownerDocument) & 1) == 0);
}
function isElementInDocumentB(node) {
return node != null && (node.nodeType == 9 || node.ownerDocument.documentElement === node || node.ownerDocument.documentElement.contains(node));
}
function isElementInDocumentC(node) {
var target = node;
while(target) {
if (target.nodeType == 9) return true;
target = target.parentNode;
}
return false;
}
return document.compareDocumentPosition ? isElementInDocumentA :
document.documentElement.contains ? isElementInDocumentB :
isElementInDocumentC ;
})();
/**
* Tapper
* tap, step, tap, roll, roll, roll ...
* @see http://d.hatena.ne.jp/brazil/20071030/1193711816
*/
function Tapper() {
this.rolling = false;
this.unbounded = false;
this.tapped = 0;
this.lastTapped = 0;
this.interval = 0;
this.previous = null;
this.defalutGap = 60;
this.minInterval = 16;
this.maxInterval = 3000;
}
(function(member) { member.apply(Tapper.prototype); })(function() {
// method
this. tap = tap; // tick down a interval between tap and tap. (ex: tapper.tap(e.keyCode); //when keydown.)
this.step = step; // call between taps to separate taps. (ex: tapper.step(); //when keyup. )
this. gap = gap; // returns adjusted interval.
this.roll = roll; // repeat given function at a regular interval which returned the gap method.
this.stop = stop; // stop rolling.
this.drop = drop; // stop rolling and clear state.
// impl
function tap(value) {
if (!this.unbounded) {
this.unbounded = true;
var now = new Date().getTime();
if (this.previous == value) {
this.tapped++;
if (this.tapped >= 2) {
this.interval = now - this.lastTapped;
}
}
else {
this.previous = value;
this.interval = -1;
this.tapped = 1;
}
this.lastTapped = now;
}
else {
if (this.previous != value) { // left tap, right tap (no step).
this.stop();
}
}
}
function step() {
this.stop();
this.unbounded = false;
}
function gap(interval) {
return (interval < 1000) ? Math.max(this.minInterval, Math.floor(interval + ((interval - 500) * 0.2))) : (interval + 100);
}
function roll(func) {
this.stop();
this.rolling = true;
var ivl = this.interval;
var fromLastTapped = new Date().getTime() - this.lastTapped;
var gap, firstRoll;
if (ivl <= 0 || ivl <= this.minInterval || ivl >= this.maxInterval) {
gap = this.defalutGap;
firstRoll = (fromLastTapped >= gap) ? 0 : (gap - fromLastTapped);
}
else {
gap = this.gap(ivl);
firstRoll = (fromLastTapped >= ivl) ? 0 : (ivl - fromLastTapped);
}
var self = this;
this.rollingTid = setInterval(function() {
func();
if (self.rollingTid) {
clearInterval(self.rollingTid);
self.rollingTid = setInterval(func, gap);
}
}, firstRoll);
//TODO keyTapper needs clear status for roll -> tap. (for don'tkeep interval.)
}
function stop() {
this.rolling = false;
if (this.rollingTid) {
clearInterval(this.rollingTid);
this.rollingTid = null;
}
}
function drop() {
this.stop();
this.tapped = 0;
this.interval = -1;
}
});
/**
* EventDispatcher
* (don't apply prototype object.)
* @see http://www.fladdict.net/blog-jp/archives/2005/06/javascript.php
* @see http://nanto.asablo.jp/blog/2007/03/23/1339498
*/
function EventDispatcher(thisObj) {
if (this instanceof EventDispatcher) {
this.addEventListener = addEventListener;
this.removeEventListener = removeEventListener;
this.dispatchEvent = dispatchEvent;
}
if (thisObj) {
thisObj.addEventListener = addEventListener;
thisObj.removeEventListener = removeEventListener;
thisObj.dispatchEvent = dispatchEvent;
}
var ec = {};
function addEventListener(type, listener) {
if (typeof listener != 'function') return;
if (ec[type] == null) { ec[type] = [listener]; return; }
var listeners = ec[type];
for (var i = 0, len = listeners.length; i < len; i++) {
if (listeners[i] === listener) { return; }
}
listeners.push(listener);
}
function removeEventListener(type, listener) {
if (ec[type] == null) return;
var listeners = ec[type];
for (var i = 0, len = listeners.length; i < len; i++) {
if (listeners[i] === listener) { listeners.splice(i, 1); break; }
}
}
function dispatchEvent(event) {
if (ec[event.type] == null) return true;
var ev = {}; for (var k in event) ev[k] = event[k]; // clone. (native Event object has readonly property.)
ev.target = this;
ev.currentTarget = this;
ev.eventPhase = 2; // supported target phase only.
var canceled = false;
ev.preventDefault = function preventDefault() { if (ev.cancelable) canceled = true; };
ev.stopPropagation = function stopPropagation() {};
var listeners = ec[ev.type];
for (var i = 0, len = listeners.length; i < len; i++) {
var res = listeners[i].call(ev.currentTarget, ev);
if (ev.cancelable) {
if (!canceled && res == false) canceled = true;
if (!canceled && ev.returnValue == false) canceled = true;
}
}
return !canceled;
}
}
// simple event creator.
// @see http://javascript.g.hatena.ne.jp/edvakf/20100329/1269881699
function createEvent(type, bubbles, cancelable, data) {
var ev;
if (!data || Browser.Opera) {
ev = document.createEvent('Event');
ev.initEvent(type, bubbles, cancelable);
if (data) ev.data = data;
}
else {
ev = document.createEvent('MessageEvent');
ev.initMessageEvent(type, bubbles, cancelable,
data, location.href, '', window
);
}
return ev;
}
/// class Scrollbars - emulate scrollbar by DOM elements.
function Scrollbars(scrollable) {
this.scrollable = scrollable;
this.isRootScrollbar = this.scrollable.isRoot();
this.minThumbSize = 20;
this.space = 17;
this.initialize();
}
(function(member) { member.apply(Scrollbars.prototype); })(function() {
var classNamePrefix = '';
Scrollbars.setClassNamePrefix = function(prefix) {
classNamePrefix = prefix;
};
var scrollbarStyle = {
position: 'absolute', display: 'block',
margin: '0', padding: '0',
zIndex: '999998',
borderWidth: '0px',
boxSizing: 'border-box',
overflow: 'visible'
};
var verticalScrollbarStyle = {
top: '0', right: '0',
width: '17px', height: '100%',
borderWidth: '1px 0px 17px 1px'
};
var horizontalScrollbarStyle = {
bottom: '0', left: '0',
width: '100%', height: '17px',
borderWidth: '1px 17px 0 1px'
};
var thumbFrameStyle = {
position: 'static', display: 'block',
margin: '0', padding: '0', border: 'none',
width: '100%',
height: '100%'
};
var thumbStyle = {
position: 'absolute', display: 'block',
margin: '0', padding: '0', border: 'none',
top: '0', left: '0'
};
var verticalThumbStyle = {
width: '100%', height: '40px'
};
var horizontalThumbStyle = {
width: '40px', height: '100%'
};
var initScrolloo = function() {
initScrolloo = function() {}
var radius = ('borderRadius' in document.documentElement.style) ? ' border-radius: 10px !important; ' : '';
var style = addStyle([
'.' + classNamePrefix + 'Scrollbar__ {',
' background-color: #BBB !important; ',
' border-style: solid !important; ',
' border-color: #BBB !important; ',
'}',
'.' + classNamePrefix + 'ThumbFrame__ {',
' background-color: #E9E9E9 !important; ',
radius,
'}',
'.' + classNamePrefix + 'Thumb__ {',
' background-color: #D0FFE9 !important; ',
radius,
'}'
].join('\n'));
style.className = classNamePrefix + 'ScrollbarsStyle__';
}
this.initialize = function initialize() {
initScrolloo();
this.h = this[1] = this.createScrollbarElements(1);
this.v = this[2] = this.createScrollbarElements(2);
this.visible = true;
this.position = 'BR';
this.layoutMethod = 'absolute';
if (this.isRootScrollbar) {
this.setLayoutMethod('fixed');
}
EventDispatcher(this);
};
this.createScrollbarElements = function(direction) {
var bar = {
scrollbar: document.createElement('div'),
thumbFrame: document.createElement('div'),
thumb: document.createElement('div')
};
bar.scrollbar.className = classNamePrefix + 'Scrollbar__';
bar.thumbFrame.className = classNamePrefix + 'ThumbFrame__';
bar.thumb.className = classNamePrefix + 'Thumb__';
applyStyle(bar.scrollbar.style, scrollbarStyle);
applyStyle(bar.thumbFrame.style, thumbFrameStyle);
applyStyle(bar.thumb.style, thumbStyle);
if (direction == 1) {
applyStyle(bar.scrollbar.style, horizontalScrollbarStyle);
applyStyle(bar.thumb.style, horizontalThumbStyle);
bar.scrollbar.className += ' ' + classNamePrefix + 'Scrollbar__Horizontal__';
}
else {
applyStyle(bar.scrollbar.style, verticalScrollbarStyle);
applyStyle(bar.thumb.style, verticalThumbStyle);
bar.scrollbar.className += ' ' + classNamePrefix + 'ScrollooScrollbar__Vertical__';
}
bar.thumbFrame.appendChild(bar.thumb);
bar.scrollbar.appendChild(bar.thumbFrame);
// draggable whole scrollbar box.
subscribe(bar.thumbFrame, 'mousedown', bind(this, this.ev_thumb_mousedown, direction));
return bar;
};
this.attach = function(container) {
if (!container) {
if (this.currentContainer) {
container = this.currentContainer;
}
else {
container = this.isRootScrollbar ? document.body : this.scrollable.element;
}
}
container.appendChild(this.h.scrollbar);
container.appendChild(this.v.scrollbar);
this.currentContainer = container; // keep previous container.
this.update();
};
this.detach = function() {
if (this.h.scrollbar.parentNode) this.h.scrollbar.parentNode.removeChild(this.h.scrollbar);
if (this.v.scrollbar.parentNode) this.v.scrollbar.parentNode.removeChild(this.v.scrollbar);
};
this.setLayoutMethod = function(absolute_or_fixed) {
if (!/^(absolute|fixed)$/.test(absolute_or_fixed)) throw new Error('invalid layout method: ' + absolute_or_fixed);
this.layoutMethod = absolute_or_fixed;
this.h.scrollbar.style.position = absolute_or_fixed;
this.v.scrollbar.style.position = absolute_or_fixed;
this.setPosition();
};
this.setPosition = function(br_bl_tr_tl) {
// default: bottom horizontal, right vertical.
var pos = (br_bl_tr_tl || this.position || 'BR').toUpperCase();
var sp = this.space + 'px', zp = '0px', hStyle, vStyle;
switch(pos) {
case 'BR':
hStyle = { top: '', left: zp, bottom: zp, borderLeftWidth: zp, borderRightWidth : sp };
vStyle = { top: zp, left: '', right : zp, borderTopWidth : zp, borderBottomWidth: sp };
break;
case 'BL':
hStyle = { top: '', left: zp, bottom: zp, borderLeftWidth: sp, borderRightWidth : zp };
vStyle = { top: zp, left: zp, right : '', borderTopWidth : zp, borderBottomWidth: sp };
break;
case 'TR':
hStyle = { top: zp, left: zp, bottom: '', borderLeftWidth: zp, borderRightWidth : sp };
vStyle = { top: zp, left: '', right : zp, borderTopWidth : sp, borderBottomWidth: zp };
break;
case 'TL':
hStyle = { top: zp, left: zp, bottom: '', borderLeftWidth: sp, borderRightWidth : zp };
vStyle = { top: zp, left: zp, right : '', borderTopWidth : sp, borderBottomWidth: zp };
break;
default:
throw new Error('invalid position: ' + pos);
}
applyStyle(this.h.scrollbar.style, hStyle);
applyStyle(this.v.scrollbar.style, vStyle);
this.position = pos;
this.update();
};
this.getScrollableInfo = function(direction) {
if (direction == 1) {
return {
clientSize: this.scrollable.getClientWidth(),
scrollSize: this.scrollable.getScrollWidth(),
scrollHead: this.scrollable.getScrollLeft()
};
}
else {
return {
clientSize: this.scrollable.getClientHeight(),
scrollSize: this.scrollable.getScrollHeight(),
scrollHead: this.scrollable.getScrollTop()
};
}
};
this.getThumbSize = function(direction) {
var info = this.getScrollableInfo(direction);
var size = (info.clientSize - this.space) * (info.clientSize / info.scrollSize) + 0.5 | 0;
if (size < this.minThumbSize) size = this.minThumbSize;
return size;
};
this.ev_thumb_mousedown = function(direction, e) {
if (this.thumbDragData) this.thumbDragData.dispose();
if (this.scrollable.isScrollable(direction)) {
var scrollbar = this[direction].scrollbar;
var thumb = this[direction].thumb;
var target = e.target;
while (target !== scrollbar && target !== thumb) { target = target.parentNode; }
if (target !== thumb) {
var offset;
if (direction == 1) {
// @see http://d.hatena.ne.jp/uupaa/20100430/1272561922
offset = ('offsetX' in e) ? e.offsetX : e.layerX;
if (!Browser.Opera && this.position.charAt(1) == 'L') {
offset -= this.space;
}
}
else {
offset = ('offsetY' in e) ? e.offsetY : e.layerY;
if (!Browser.Opera && this.position.charAt(0) == 'T') {
offset -= this.space;
}
}
var info = this.getScrollableInfo(direction), size = this.getThumbSize(direction), sp = this.space;
var dest = (offset - size * 0.45) * (info.scrollSize / (info.clientSize - sp)) + 0.5 | 0;
this.scrollable.scrollDirectTo(direction, dest);
}
e.preventDefault();
if (Browser.Opera && window.top !== window.self && this.isRootScrollbar) {
window.focus();
}
var thumbDragData = {
direction: direction,
startClientX: e.clientX,
startClientY: e.clientY,
startScrollLeft: this.scrollable.getScrollLeft(),
startScrollTop: this.scrollable.getScrollTop()
};
if (this.dispatchEvent(createEvent('ScrollooStartThumbing', false, true, thumbDragData))) {
thumbDragData.dispose = runnable(
subscribe(document, 'mousemove', bind(this, this.ev_thumb_dragging)),
subscribe(document, 'mouseup', bind(this, this.ev_thumb_draggend))
);
this.thumbDragData = thumbDragData;
}
else {
this.dispatchEvent(createEvent('ScrollooEndThumbing', false, false, thumbDragData));
}
}
};
this.ev_thumb_dragging = function(e) {
// The e.clientY value after scrolling becomes strange on Opera.
if (Browser.Opera) {
var context = getContextData();
if (context.Scrollbar_thumb_dragging_session) return;
context.Scrollbar_thumb_dragging_session = true;
}
var data = this.thumbDragData;
var start, diff;
if (data.direction == 1) {
start = data.startScrollLeft;
diff = e.clientX - data.startClientX;
}
else {
start = data.startScrollTop;
diff = e.clientY - data.startClientY;
}
var info = this.getScrollableInfo(data.direction), thumbSize = this.getThumbSize(data.direction), sp = this.space;
var dest = start + (diff * ((info.scrollSize - info.clientSize) / (info.clientSize - thumbSize - sp)) + 0.5 | 0);
this.scrollable.scrollDirectTo(data.direction, dest);
this.update();
this.dispatchEvent(createEvent('ScrollooThumbing', false, false));
}
this.ev_thumb_draggend = function(e) {
var data = this.thumbDragData;
if (this.thumbDragData) {
this.thumbDragData.dispose();
this.thumbDragData = null;
}
this.dispatchEvent(createEvent('ScrollooEndThumbing', false, false, data));
}
this.show = function show() {
this.visible = true;
this.update();
};
this.hide = function hide() {
this.visible = false;
this.h.scrollbar.style.display = 'none';
this.v.scrollbar.style.display = 'none';
};
this.layout = function() {
if (!this.visible) return;
if (!this.currentContainer) return;
if (this.isRootScrollbar || this.currentContainer !== this.scrollable.element) {
this.update();
}
else {
// reflow by force.
var sL = this.scrollable.getScrollLeft();
this.scrollable.scrollTo(0, null);
this.hide();
this.scrollable.scrollTo(sL, null);
this.show();
}
};
this.update = function update() {
if (this.visible) {
this.update_impl();
}
};
// This implementation is redundant but fast.
this.update_impl = function update_impl() {
var scr = this.scrollable;
var cW = scr.getClientWidth();
var cH = scr.getClientHeight();
var sW = scr.getScrollWidth();
var sH = scr.getScrollHeight();
var sL = scr.getScrollLeft();
var sT = scr.getScrollTop();
var h_scr_style = this.h.scrollbar.style;
var v_scr_style = this.v.scrollbar.style;
var h_thu_style = this.h.thumb.style;
var v_thu_style = this.v.thumb.style;
var min = this.minThumbSize
var sp = this.space;
// set scrollbar position.
if (this.layoutMethod == 'absolute') {
// horizontal.
var left = sL + 'px';
if (h_scr_style.left != left) h_scr_style.left = left;
if (this.position.charAt(0) == 'T') {
var top = sT + 'px';
if (h_scr_style.top != top) h_scr_style.top = top;
}
else {
var bottom = -sT + 'px';
if (h_scr_style.bottom != bottom) h_scr_style.bottom = bottom;
}
// vertical.
var top = sT + 'px';
if (v_scr_style.top != top) v_scr_style.top = top;
if (this.position.charAt(1) == 'L') {
var left = sL + 'px';
if (v_scr_style.left != left) v_scr_style.left = left;
}
else {
var right = -sL + 'px';
if (v_scr_style.right != right) v_scr_style.right = right;
}
}
// set thumb position, size.
// horizontal.
if (sW <= cW) {
if (h_scr_style.display != 'none') h_scr_style.display = 'none';
if (h_thu_style.display != 'none') h_thu_style.display = 'none';
}
else {
var head = (cW - sp) * (sL / sW) + 0.5 | 0;
var size = (cW - sp) * (cW / sW) + 0.5 | 0;
if (size < min) {
head = (cW - (min + sp)) * (sL / (sW - cW)) + 0.5 | 0;
size = min;
}
head += 'px'; size += 'px';
if (h_thu_style.left != head ) h_thu_style.left = head;
if (h_thu_style.width != size ) h_thu_style.width = size;
if (h_thu_style.display != 'block') h_thu_style.display = 'block';
if (this.visible) {
if (h_scr_style.display != 'block') h_scr_style.display = 'block';
}
}
// vertical.
if (sH <= cH) {
if (v_scr_style.display != 'none') v_scr_style.display = 'none';
if (v_thu_style.display != 'none') v_thu_style.display = 'none';
}
else {
var head = (cH - sp) * (sT / sH) + 0.5 | 0;
var size = (cH - sp) * (cH / sH) + 0.5 | 0;
if (size < min) {
head = (cH - (min + sp)) * (sT / (sH - cH)) + 0.5 | 0;
size = min;
}
head += 'px'; size += 'px';
if (v_thu_style.top != head ) v_thu_style.top = head;
if (v_thu_style.height != size ) v_thu_style.height = size;
if (v_thu_style.display != 'block') v_thu_style.display = 'block';
if (this.visible) {
if (v_scr_style.display != 'block') v_scr_style.display = 'block';
}
}
};
});
/// class ScrollbarBehavior - implements scrollbar's behavior(like showing or hiding).
function ScrollbarBehavior(scrollbars) {
this.scrollbars = scrollbars;
this.scrollable = scrollbars.scrollable;
this.scrollData = {
'1' : { body: scrollbars.h, prop: {} },
'2' : { body: scrollbars.v, prop: {} }
};
this.initialize();
}
ScrollbarBehavior.Option = {
opacity_positive: 0.9,
opacity_normal: 0.7,
opacity_negative: 0.4,
show_onscroll: true
};
(function(member) { member.apply(ScrollbarBehavior.prototype); })(function() {
this.initialize = function() {
this.option = overridable(ScrollbarBehavior.Option);
this.status = {};
this.scrollData[1].prop.opacityAnim = new Smooth(this.scrollData[1].body.scrollbar.style, 'opacity', 800);
this.scrollData[2].prop.opacityAnim = new Smooth(this.scrollData[2].body.scrollbar.style, 'opacity', 800);
this.attachEvents();
this.updateStatus();
this.scrollData[1].body.scrollbar.style.opacity = 0;
this.scrollData[2].body.scrollbar.style.opacity = 0;
};
this.attachEvents = function() {
if (this.detachEvents) this.detachEvents();
var detach = [];
for (var i = 0; i < 2; i++) with({h1v2:[1,2][i]}) with({sb:this.scrollData[h1v2].body}) {
detach.push(
subscribe(sb.scrollbar, 'mousedown', bind(this, this.ev_scrollbar_mousedown, h1v2)),
subscribe(sb.scrollbar, 'mouseover', bind(this, this.ev_scrollbar_mouseover, h1v2)),
subscribe(sb.scrollbar, 'mousemove', bind(this, this.ev_scrollbar_mousemove, h1v2)),
subscribe(sb.scrollbar, 'mouseout', bind(this, this.ev_scrollbar_mouseout, h1v2)),
subscribe(sb.scrollbar, 'dblclick', bind(this, this.ev_scrollbar_dblclick, h1v2))
);
}
detach.push(subscribe(this.scrollbars, 'ScrollooStartThumbing', bind(this, this.ev_thumb_startThumbing)));
detach.push(subscribe(this.scrollbars, 'ScrollooEndThumbing', bind(this, this.ev_thumb_endThumbing)));
if (this.scrollable.isRoot()) {
detach.push(subscribe(window, 'scroll', bind(this, this.ev_view_scroll)));
}
else {
detach.push(subscribe(this.scrollable.element, 'scroll', bind(this, this.ev_view_scroll)));
}
this.detachEvents = function() { if (detach) run(detach); detach = null; };
};
this.ev_scrollbar_mousedown = function(h1v2, e) {
this.showScrollbar(h1v2, 1300);
};
this.ev_scrollbar_mouseover = function(h1v2, e) {
if (this.scrollable.isScrollable(h1v2)) {
this.showScrollbarLater(h1v2);
}
};
this.ev_scrollbar_mousemove = function(h1v2, e) {
var sb = this.scrollData[h1v2];
if (sb.prop.showing) {
this.showScrollbar(h1v2, 1300);
}
};
this.ev_scrollbar_mouseout = function(h1v2, e) {
this.hideScrollbarLater(h1v2, 650);
};
this.ev_scrollbar_dblclick = function(h1v2, e) {
this.killScrollbar(h1v2);
e.returnValue = false;
};
this.ev_thumb_startThumbing = function(e) {
this.holdScrollbarOpacity(e.data.direction, this.option.opacity_positive);
};
this.ev_thumb_endThumbing = function(e) {
this.clearHoldScrollbarOpacity(e.data.direction);
this.showScrollbar(e.data.direction, 1300);
};
this.ev_view_scroll = function(e) {
if (e.eventPhase != 2) return;
var scr = this.scrollable;
var h1v2;
if (this.status.scrollLeft != scr.getScrollLeft()) {
h1v2 = 1;
}
else if (this.status.scrollTop != scr.getScrollTop()) {
h1v2 = 2;
}
else {
return;
}
var sb = this.scrollData[h1v2];
if (sb.prop.showing) {
var time = (sb.prop.opacity == this.option.opacity_negative) ? 700 : 1300;
this.showScrollbar(h1v2, time, sb.prop.opacity);
}
else if (scr.isScrollable(h1v2)) {
if (scr.isRoot()) {
// for performance.
if (sb.prop.scrolling_tid) clearTimeout(sb.prop.scrolling_tid);
this.scrollbars.hide();
sb.prop.scrolling_tid = setTimeout(bind(this, function() {
this.scrollbars.show();
if (this.option.show_onscroll) {
this.showScrollbar(h1v2, 1300, this.option.opacity_negative);
}
}), 250);
}
else {
this.showScrollbar(h1v2, 700, this.option.opacity_negative);
}
}
this.updateStatus();
};
this.updateStatus = function() {
this.status.scrollTop = this.scrollable.getScrollTop();
this.status.scrollLeft = this.scrollable.getScrollLeft();
};
this.showScrollbar = function showScrollbar(h1v2, hideAfter, opacity) {
var sb = this.scrollData[h1v2];
if (sb.prop.holding) return;
this.hideScrollbar(h1v2 == 1 ? 2 : 1);
this.scrollbars.update();
sb.prop.opacity = sb.body.scrollbar.style.opacity = opacity || this.option.opacity_normal;
sb.prop.showing = true;
this.hideScrollbarLater(h1v2, hideAfter);
};
this.showScrollbarLater = function showScrollbar(h1v2, milliseconds, hideAfter, opacity) {
var sb = this.scrollData[h1v2];
if (sb.prop.holding) return;
if (sb.prop.showing) {
this.showScrollbar(h1v2, hideAfter, opacity);
return;
}
if (!sb.prop.show_id) {
var later = runnable(
bind(this, this.clearShowScrollbarLater, h1v2),
bind(this, this.showScrollbar, h1v2, hideAfter, opacity)
);
sb.prop.show_id = setTimeout(later, milliseconds || 80);
}
};
this.clearShowScrollbarLater = function showScrollbar(h1v2) {
var sb = this.scrollData[h1v2];
if (sb.prop.holding) return;
if (sb.prop.show_id) clearTimeout(sb.prop.show_id);
sb.prop.show_id = null;
};
this.hideScrollbar = function hideScrollbar(h1v2, immediate) {
this.clearShowScrollbarLater(h1v2);
var sb = this.scrollData[h1v2];
if (sb.prop.holding) return;
sb.prop.showing = false;
if (immediate) {
sb.prop.opacityAnim.clear();
sb.body.scrollbar.style.opacity = 0;
}
else {
sb.prop.opacityAnim.go('0.00');
}
};
this.hideScrollbarLater = function hideScrollbarLater(h1v2, milliseconds) {
var sb = this.scrollData[h1v2];
if (sb.prop.holding) return;
this.clearShowScrollbarLater(h1v2);
this.clearHideScrollbarLater(h1v2);
sb.prop.hide_id = setTimeout(bind(this, this.hideScrollbar, h1v2), milliseconds || 1300);
};
this.clearHideScrollbarLater = function clearHideScrollbarLater(h1v2) {
var sb = this.scrollData[h1v2];
if (sb.prop.holding) return;
if (sb.prop.hide_id) clearTimeout(sb.prop.hide_id);
sb.prop.hide_id = null;
sb.prop.opacityAnim.clear();
};
this.holdScrollbarOpacity = function holdScrollbarOpacity(h1v2, opacity) {
this.clearShowScrollbarLater(h1v2);
this.clearHideScrollbarLater(h1v2);
var sb = this.scrollData[h1v2];
sb.prop.holding = true;
sb.prop.opacity = sb.body.scrollbar.style.opacity = opacity;
};
this.clearHoldScrollbarOpacity = function clearHoldScrollbarOpacity(h1v2) {
var sb = this.scrollData[h1v2];
sb.prop.holding = false;
};
this.killScrollbar = function killScrollbar(h1v2, restoreAfter) {
this.hideScrollbar(h1v2);
var sb = this.scrollData[h1v2];
sb.body.detach();
sb.prop.kill_id = setTimeout(bind(this, this.restoreScrollbar, h1v2), restoreAfter || 5000);
};
this.restoreScrollbar = function restoreScrollbar(h1v2) {
var sb = this.scrollData[h1v2];
sb.body.attach();
sb.prop.kill_id = null;
};
});
/// class Scrolloo - Controller of scrollbars.
function Scrolloo(container, option) {
this.initialize.apply(this, arguments);
}
Scrolloo.Option = {
smooth_scroll: true,
scroll_unit: 120,
backwardDistance: function(scr) { return scr.scrollable.getClientHeight() / 2; },
forwardDistance : function(scr) { return scr.scrollable.getClientHeight() / 2; },
pageupDistance : function(scr) { return scr.scrollable.getClientHeight(); },
pagedownDistance: function(scr) { return scr.scrollable.getClientHeight(); },
endDistance : function(scr) { return scr.scrollable.getScrollHeight(); },
homeDistance : function(scr) { return scr.scrollable.getScrollHeight(); },
leftDistance : function(scr) { return this.scroll_unit; },
upDistance : function(scr) { return this.scroll_unit; },
rightDistance : function(scr) { return this.scroll_unit; },
downDistance : function(scr) { return this.scroll_unit; }
};
(function(member) { member.apply(Scrolloo.prototype); })(function() {
// class initialize.
Scrollbars.setClassNamePrefix('Scrolloo');
var lastClickedTarget = null;
function setLastClicked(targetElement) {
var target = targetElement;
if (target && target.nodeType == 3) target = target.parentNode;
while(target && target.nodeType == 1) {
var style = document.defaultView.getComputedStyle(target, '');
if (style.display == 'block') break;
target = target.parentNode;
}
if (targetElement === lastClickedTarget) { // toggle.
lastClickedTarget = null;
}
else {
lastClickedTarget = targetElement;
}
}
function lastClicked(container) {
var target = lastClickedTarget;
while(target) {
if (target === container) return true;
target = target.parentNode;
}
return false;
}
function document_click(e) {
setLastClicked(e.target);
}
function window_blur(e) {
setLastClicked(null);
}
var currentClientX = -1;
var currentClientY = -1;
function document_mousemove(e) {
e.currentClientX = e.clientX;
e.currentClientY = e.clientY;
}
var keydownCode = -1;
var keypressCode = -1;
var keyTapper = new Tapper();
function document_keydown(e) {
if (!('repeat' in e)) {
if (keydownCode == e.keyCode) e.repeat = true;
keydownCode = e.keyCode;
}
if (!Browser.Opera) processKey(e);
}
function document_keypress(e) {
if (!('repeat' in e)) {
if (keypressCode == e.keyCode) e.repeat = true;
keypressCode = e.keyCode;
}
if (Browser.Opera) processKey(e);
}
function document_keyup(e) {
keydownCode = -1;
keypressCode = -1;
keyTapper.step();
}
var doscrolled = false;
function processKey(e) {
keyTapper.tap(e.keyCode);
if (e.returnValue === false) return; // already prevent default.
if (!checkTarget(e)) return;
var code = e.keyCode;
var command;
switch(code) {
case 32: // space
if (!e.ctrlKey && !e.altKey && !e.metaKey)
command = { command: e.shiftKey ? 'backward' : 'forward' };
break;
case 33: // PageUp
if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey)
command = { command: 'pageup' };
break;
case 34: // PageDown
if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey)
command = { command: 'pagedown' };
break;
case 35: // End
if (!e.shiftKey && !e.altKey && !e.metaKey)
command = { command: 'end' };
break;
case 36: // Home
if (!e.shiftKey && !e.altKey && !e.metaKey)
command = { command: 'home' };
break;
case 37: // left
if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey)
command = { command: 'left' };
break;
case 38: // up
if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey)
command = { command: 'up' };
break;
case 39: // right
if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey)
command = { command: 'right' };
break;
case 40: // down
if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey)
command = { command: 'down' };
break;
default:
break;
}
if (command) {
if (keyTapper.rolling) {
e.preventDefault();
}
else if (e.repeat && doscrolled) {
e.preventDefault();
keyTapper.roll(function() {
dispatchDoScrollEvent(command);
});
}
else if (dispatchDoScrollEvent(command) == false) {
e.preventDefault();
doscrolled = true;
}
else {
doscrolled = false;
}
}
}
function checkTarget(e) {
if (e.eventPhase == 2) return true;
if (/^(?:TEXTAREA|INPUT|SELECT|BUTTON)$/.test(e.target.nodeName)) return false;
return true;
}
function dispatchDoScrollEvent(command) {
var ev = createEvent('ScrollooDoScroll', true, true, command);
var target = document.activeElement;
if (!target || target === document.body) target = lastClickedTarget || document.body;
return target.dispatchEvent(ev);
}
Scrolloo.finalize = runnable(
subscribe(document, 'click', document_click),
subscribe(window, 'blur', window_blur),
subscribe(document, 'mousemove', document_mousemove),
subscribe(document, 'keydown', document_keydown),
subscribe(document, 'keypress', document_keypress),
subscribe(document, 'keyup', document_keyup)
);
// instance members.
this.initialize = function initialize(container, option) {
this.option = overridable(Scrolloo.Option);
applyProperty(this.option, option);
this.container = container || Sfn.getRoot();
this.scrollable = new Scrollable(this.container);
this.scrollbars = new Scrollbars(this.scrollable);
this.behavior = new ScrollbarBehavior(this.scrollbars);
this.wheelTapper = new Tapper();
this.wheelTapper.defaultGap = 30;
this.wheelTapper.minInterval = 30;
this.attach();
};
this.attach = function(e) {
if (this.barckup) this.detach();
var overflow_container = this.scrollable.isRoot() ? document.documentElement : this.container;
this.barckup = {
overflow_container: overflow_container,
container_overflow_x: overflow_container.style.overflowX,
container_overflow_y: overflow_container.style.overflowY,
container_position: this.container.style.position
};
// set absolute origin for scrollbars.
if (!this.scrollable.isRoot()) {
if (document.defaultView.getComputedStyle(this.container, null).position == 'static') {
this.barckup.container_position_changed = true;
this.container.style.position = 'relative';
}
}
overflow_container.style.overflow = 'hidden';
// hide body scrollbar (Google search result. etc. )
if (this.scrollable.isRoot() && this.container != document.body) {
var computedStyle = document.defaultView.getComputedStyle(document.body, null);
if (computedStyle.overflowX == 'scroll') {
this.barckup.body_overflow_x = document.body.style.overflowX;
document.body.style.overflowX = 'visible';
}
if (computedStyle.overflowY == 'scroll') {
this.barckup.body_overflow_y = document.body.style.overflowY;
document.body.style.overflowY = 'visible';
}
}
this.scrollbars.attach();
this.attachEvents();
this.attached = true;
};
this.detach = function(e) {
this.scrollbars.detach();
this.detachEvents();
if (this.barckup.container_position_changed) {
this.container.style.position = this.barckup.container_position;
}
this.barckup.overflow_container.style.overflowX = this.barckup.container_overflow_x;
this.barckup.overflow_container.style.overflowY = this.barckup.container_overflow_y;
if (this.barckup.body_overflow_x != null) {
document.body.style.overflowX = this.barckup.body_overflow_x;
}
if (this.barckup.body_overflow_y != null) {
document.body.style.overflowY = this.barckup.body_overflow_y;
}
this.barckup = null;
this.attached = false;
};
this.attachEvents = function() {
if (this.detachEvents) this.detachEvents();
var detach = [];
if (this.scrollable.isRoot()) {
detach.push(subscribe(window, 'scroll', bind(this, this.ev_view_scroll)));
detach.push(subscribe(document, 'mousewheel', bind(this, this.ev_view_wheel)));
detach.push(subscribe(document, 'ScrollooDoScroll', bind(this, this.ev_do_scroll)));
}
else {
detach.push(subscribe(this.container, 'scroll', bind(this, this.ev_view_scroll)));
detach.push(subscribe(this.container, 'mousewheel', bind(this, this.ev_view_wheel)));
detach.push(subscribe(this.container, 'ScrollooDoScroll', bind(this, this.ev_do_scroll)));
if (Browser.Opera) {
// for Opera's link drag.
var mousedown = bind(this, this.ev_view_mousedown);
var dispose_mousedown_impl = null;
var subscribe_mousedown = bind(this, function() {
if (!dispose_mousedown_impl) dispose_mousedown_impl = subscribe(this.container, 'mousedown', mousedown);
return dispose_mousedown;
});
var dispose_mousedown = function() {
if (dispose_mousedown_impl) dispose_mousedown_impl();
dispose_mousedown_impl = null;
};
detach.push(subscribe(this.container, 'mouseover', function(e) {
var target = e.target;
while (target != e.currentTarget) {
if (target.nodeName == 'A') { dispose_mousedown(); break; }
target = target.parentNode;
}
}));
detach.push(subscribe(this.container, 'mouseout', subscribe_mousedown));
detach.push(subscribe_mousedown());
}
else {
detach.push(subscribe(this.container, 'mousedown', bind(this, this.ev_view_mousedown)));
}
}
if (Browser.Opera && !this.scrollable.isRoot()) {
// When the overflow property of ancestor's container is changed, this container's scroll position is reset(0, 0).
// Therefore, it is necessary to update it as much as possible.
//detach.push(subscribe(this.container, 'mousemove', bind(this, this.update)));
}
// delay attach for heavy initializing page. (like the fastladder.)
setTimeout(bind(this, function() {
if (detach) {
var layout = bind(this.scrollbars, this.scrollbars.layout);
var layoutLater = function() {
dispose_domobserve();
setTimeout(function() {
layout();
subscribe_domobserve();
}, 160);
};
var dispose_domobserve_impl = null;
var subscribe_domobserve = bind(this, function() {
if (!dispose_domobserve_impl) {
dispose_domobserve_impl = runnable(
subscribe(this.container, 'DOMNodeInserted', layoutLater),
subscribe(this.container, 'DOMNodeRemoved', layoutLater)
);
}
return dispose_domobserve;
});
var dispose_domobserve = function() {
if (dispose_domobserve_impl) dispose_domobserve_impl();
dispose_domobserve_impl = null;
};
detach.push(subscribe(window, 'resize', layoutLater));
detach.push(subscribe_domobserve());
}
}), 2000);
this.detachEvents = function() { if (detach) run(detach); detach = null; };
};
this.ev_view_scroll = function(e) {
if (e.eventPhase != 2) return;
this.scrollbars.update();
};
this.ev_view_mousedown = function(e) {
this.clearDragData();
if (!this.scrollable.isRoot()) {
var target = e.target;
var on_sb = false;
if (target == document.documentElement) {
target = this.container;
}
if (target != this.container && Sfn.isScrollable(target)) {
// ignore on native scrollbar.
if (e.offsetX < target.clientLeft || e.offsetX > target.clientWidth + target.clientLeft) return;
if (e.offsetX < target.clientTop || e.offsetY > target.clientHeight + target.clientTop) return;
// :P
if (target.nodeName == 'TEXTAREA' && Browser.Opera) return;
}
while(target != this.container) {
if (target == this.scrollbars.h.scrollbar) { on_sb = true; break; }
if (target == this.scrollbars.v.scrollbar) { on_sb = true; break; }
target = target.parentNode;
}
if (!on_sb) {
//drag scroll support.
this.viewDragData = {
startTarget: e.target,
dispose: runnable(
subscribe(document, 'mousemove', bind(this, this.ev_view_dragging)),
subscribe(document, 'mouseup', bind(this, this.ev_view_draggend))
)
};
}
}
};
this.ev_view_dragging = function(e) {
var data = this.viewDragData, scr = this.scrollable;
var pos = getAbsolutePosition(this.container);
var dx = null, dy = null, num = 0;
if ((num = e.pageX - (pos.x + this.container.clientLeft)) < 0) {
dx = 30 * ((num / 20 | 0) - 1);
}
else if ((num = e.pageX - (pos.x + this.container.clientWidth + this.container.clientLeft)) > 0) {
dx = 30 * ((num / 20 | 0) + 1);
}
if ((num = e.pageY - (pos.y + this.container.clientTop)) < 0) {
dy = 30 * ((num / 20 | 0) - 1);
}
else if ((num = e.pageY - (pos.y + this.container.clientHeight + this.container.clientTop)) > 0) {
dy = 30 * ((num / 20 | 0) + 1);
}
if (dx == null && dy == null) {
if (data.scrollTid) clearInterval(data.scrollTid);
data.scrollTid = null;
}
else {
data.dx = dx, data.dy = dy;
if (!data.scrollTid) {
data.scrollTid = setInterval(function() {
scr.scrollBy(data.dx, data.dy);
}, 100);
}
}
};
this.ev_view_draggend = function(e) {
this.clearDragData();
};
this.clearDragData = function(e) {
if (this.viewDragData) {
if (this.viewDragData.scrollTid) clearInterval(this.viewDragData.scrollTid);
this.viewDragData.dispose();
this.viewDragData = null;
}
}
this.ev_view_wheel = function(e) {
// for nested scrollbars. (If the inner scrollbars scrolled, the outer scrollbars do nothing.)
if (e.returnValue === false) return;
var target = e.target;
var axis = e.axis || 2; // firefox's MouseScrollEvent has axis property.
var u1d2 = wheelDelta(e) > 0 ? 1 : 2;
var on_sb = false;
if (target == document.documentElement) {
target = this.container;
}
while(target != this.container) {
if (target == this.scrollbars.v.scrollbar) { on_sb = true; axis = 2; break; }
if (target == this.scrollbars.h.scrollbar) { on_sb = true; axis = 1; break; }
if (Sfn.isScrollableNode(target, axis) && Sfn.isScrollable(target, axis, u1d2)) break;
target = target.parentNode;
}
if (on_sb || target == this.container) {
var do_scroll = this.scrollable.isScrollable(axis, u1d2);
// scroll horizontal if it coundn't scroll vertical both directions and it scrollable horizontal to wheeled direction.
if (!do_scroll && axis == 2) {
if (!this.scrollable.isScrollable(2) && this.scrollable.isScrollable(1, u1d2)) {
if ((this.scrollable.isRoot() && window.top === window.self) || lastClicked(this.container)) {
axis = 1;
do_scroll = true;
}
}
}
// tap.
this.wheelTapper.tap(axis + ':' + u1d2);
this.wheelTapper.step();
// native vertical scrolling for Opera by wheel on root container.
// (prevent flickering while wheeling on iframe -> root window)
// Opera native wheel couldn't scroll on textarea -> other.
if (do_scroll && axis == 2 && (Browser.Opera && Browser.Opera_version < 11.5)&& (this.scrollable.isRoot() && e.target.nodeName != 'TEXTAREA') && !(on_sb || e.altKey)) {
do_scroll = false;
}
else // :P
if (do_scroll || (on_sb && this.scrollable.isScrollable(axis)) || (lastClicked(this.container) && this.scrollable.isScrollable())) {
e.preventDefault();
e.returnValue = false; // flag for nested scrollbars.
}
if (do_scroll) {
var delta = wheelDelta(e);
this.scrollable.scrollDirectBy(axis, -delta * this.option.scroll_unit, this.option.smooth_scroll);
if (on_sb || e.metaKey) {
if (this.wheelTapper.interval > this.wheelTapper.maxInterval) {
this.wheelTapper.drop();
}
else if (
(this.wheelTapper.tapped == 2 && this.wheelTapper.interval >= 300) ||
this.wheelTapper.tapped >= 3 ||
this.wheelTapper.rolling
) {
//TODO change movedown -> wheel
//TODO want to move mouse freely when rolling.
var cX = e.clientX, cY = e.clientY;
var dispose = subscribe(document, 'mousemove', bind(this, (function(e) {
if (Math.abs(e.clientX - cX) > 5 || Math.abs(e.clientY - cY) > 5) {
this.wheelTapper.drop();
dispose();
}
})));
this.wheelTapper.roll(bind(this, function() {
if (this.scrollable.isScrollable(axis, u1d2)) {
this.scrollable.scrollDirectBy(axis, -delta * this.option.scroll_unit, this.option.smooth_scroll);
}
else {
this.wheelTapper.drop();
dispose();
}
}));
}
}
}
}
};
this.ev_do_scroll = function(e) {
// do nothing on root container when it is fullscreen mode. (for Opera Show)
if (this.scrollable.isRoot()) {
if (window.screenLeft == 0 &&
window.screenTop == 0 &&
this.scrollable.getClientWidth() == window.screen.width &&
this.scrollable.getClientHeight() == window.screen.height
) return;
}
var cmd = e.data.command;
var axis, u1d2, dist, smooth = this.option.smooth_scroll;
switch(cmd) {
case 'backward': axis = 2; u1d2 = 1; dist = this.option.backwardDistance(this); break;
case 'forward': axis = 2; u1d2 = 2; dist = this.option.forwardDistance(this); break;
case 'pageup': axis = 2; u1d2 = 1; dist = this.option.pageupDistance(this); break;
case 'pagedown': axis = 2; u1d2 = 2; dist = this.option.pagedownDistance(this); break;
case 'end': axis = 2; u1d2 = 2; dist = this.option.endDistance(this); smooth = false; break;
case 'home': axis = 2; u1d2 = 1; dist = this.option.homeDistance(this); smooth = false; break;
case 'left': axis = 1; u1d2 = 1; dist = this.option.leftDistance(this); break;
case 'up': axis = 2; u1d2 = 1; dist = this.option.upDistance(this); break;
case 'right': axis = 1; u1d2 = 2; dist = this.option.rightDistance(this); break;
case 'down': axis = 2; u1d2 = 2; dist = this.option.downDistance(this); break;
default: return;
}
var target = e.target;
var on_sb = false;
if (target == document.documentElement) {
target = this.container;
}
while(target != this.container) {
if (target == this.scrollbars.v.scrollbar) { on_sb = true; axis = 2; break; }
if (target == this.scrollbars.h.scrollbar) { on_sb = true; axis = 1; break; }
if (Sfn.isScrollableNode(target, axis) && Sfn.isScrollable(target, axis, u1d2)) break;
target = target.parentNode;
}
if (on_sb || target == this.container) {
var do_scroll = this.scrollable.isScrollable(axis, u1d2);
if (do_scroll || (on_sb && this.scrollable.isScrollable(axis))) {
e.preventDefault();
e.stopPropagation();
}
if (do_scroll) {
this.scrollable.scrollDirectBy(axis, u1d2 == 1 ? -dist: dist, smooth);
}
}
};
});
/// class Namespace.
function Namespace(name, global) {
if (this instanceof Namespace) return this;
if (global == null) global = (function() { return this; })();
return global[name] || (global[name] = new Namespace());
}
Namespace.prototype.extend = function use(props) {
for(var k in props) if (!(k in this)) {
this[k] = props[k];
}
};
Namespace.prototype.use = function use(target) {
if (!target) target = (function() { return this; })();
for(var k in this) if (this.hasOwnProperty(k)) {
target[k] = this[k];
}
};
Namespace.prototype.unuse = function unuse(target) {
if (!target) target = (function() { return this; })();
for(var k in this) if (target[k] === this[k]) {
delete target[k];
}
};
Namespace.prototype.toString = function toString() {
return '[object Namespace]';
};
var ns = Namespace(SCROLLOO_NAMESPACE, window.opera || window);
ns.extend({
Smooth: Smooth,
Scrollable: Scrollable,
Scrollbars: Scrollbars,
ScrollbarBehavior: ScrollbarBehavior,
Scrolloo: Scrolloo,
addStyle: addStyle
});
var startOverflow = {
x: document.documentElement.style.overflowX,
y: document.documentElement.style.overflowY
};
document.documentElement.style.overflow = 'hidden'; // hide scrollbars as soon as possible.
var rootScrollbar = null;
var disposeObserve = null;
function initScrollbar() {
rootScrollbar = new Scrolloo();
rootScrollbar.behavior.option.show_onscroll = false;
rootScrollbar.detach = function(eternally) {
Scrolloo.prototype.detach.call(rootScrollbar);
document.documentElement.style.overflowX = startOverflow.x;
document.documentElement.style.overflowY = startOverflow.y;
if (eternally) {
if (disposeObserve) {
disposeObserve();
disposeObserve = null;
}
rootScrollbar = null;
ns.Scrollbars.ROOT_SCROLLBAR = null;
}
};
ns.Scrolloo.ROOT_SCROLLBAR = rootScrollbar;
//toggle on triple click.
disposeObserve = subscribe(document, 'click', function(e) {
if (e.returnValue !== false) {
if(e.detail == 3) {
var target = e.target;
if (target.nodeType != 3 && !/^(INPUT|TEXTAREA|SELECT|IMG|OBJECT|IFRAME|SVG)$/.test(target.nodeName)) {
if (target.ownerDocument.defaultView.getSelection() == '') {
if (rootScrollbar.attached) rootScrollbar.detach();
else rootScrollbar.attach();
}
}
}
}
});
document.addEventListener('ScrollooEnable', function() {
if (!rootScrollbar.attached) rootScrollbar.attach();
}, false);
document.addEventListener('ScrollooDisable', function() {
if (rootScrollbar.attached) rootScrollbar.detach();
}, false);
var registered = [];
function registerScroller(targets) {
}
document.addEventListener('ScrollooAttach', function(e) {
var data = e.data;
if (!data) return;
var cssQuery = e.data;
if (!cssQuery || cssQuery.indexOf('*') >= 0) return;
var items = document.querySelectorAll(cssQuery);
for (var i = 0, len = items.length; i < len; i++) {
if (Scrollable.fn.isScrollableNode(items[i])) {
var sb = new Scrolloo(items[i]);
}
}
}, false);
document.addEventListener('ScrollooDetach', function(e) {
var cssQuery = e.data;
if (!cssQuery || cssQuery.indexOf('*') >= 0) return;
var items = document.querySelectorAll(cssQuery);
for (var i = 0, len = items.length; i < len; i++) {
if (Scrollable.fn.isScrollableNode(items[i])) {
var sb = new Scrolloo(items[i]);
}
}
}, false);
return;
//TODO
if (getPref('ondemandscrollbar_killScrollbar')) {
killScrollbar();
}
}
var scrollbarWidth = 16;
function killScrollbar() {
setPref('ondemandscrollbar_killScrollbar', '1');
if (scrollbar.offsetWidth) scrollbarWidth = scrollbar.offsetWidth;
rootScrollbar.detach();
setTimeout(function() {
document.addEventListener('dblclick', observeDblclick, false);
}, 100);
}
function observeDblclick(e) {
if (e.pageX >= root.clientWidth - scrollbarWidth) {
restoreScrollbar();
}
}
function restoreScrollbar() {
setPref('ondemandscrollbar_killScrollbar', null);
document.removeEventListener('dblclick', observeDblclick, false);
rootScrollbar.attach();
showScrollbar(2000);
}
function getPref(key) {
if (!window.localStorage) return null;
return window.localStorage[key];
}
function setPref(key, value) {
if (!window.localStorage) return;
if (value == null) {
localStorage.removeItem(key);
}
else {
window.localStorage[key] = value;
}
}
function main() {
if (!document.body) return;
initScrollbar();
}
function ready(func) {
if (document.getElementsByTagName('body')[0]) func();
else document.addEventListener('DOMContentLoaded', func, false);
}
ready(main);
})();
// ==UserScript==
// @name scrolloo plugin - fastladder
// @version 1.00
// @include http://fastladder.com/reader/
// @include http://reader.livedoor.com/reader/
// ==/UserScript==
(function() {
if (location.href.indexOf('http://fastladder.com/reader/') == 0 ||
location.href.indexOf('http://reader.livedoor.com/reader/') == 0
); else return;
function Namespace(name, global) {
if (this instanceof Namespace) return this;
if (global == null) global = (function() { return this; })();
return global[name] || (global[name] = new Namespace());
}
var opera = window.opera;
function init() {
with ({ns:Namespace('http://www.hatena.ne.jp/miya2000/scrolloo', opera || window)}) with(ns) {
var sc = document.getElementById('subs_container');
if (document.defaultView.getComputedStyle(sc).overflow == 'scroll') {
sc.style.boxSizing = 'border-box';
sc.style.borderBottom = 'transparent solid 16px';
if ('WebkitBoxSizing' in sc.style) {
sc.style.WebkitBoxSizing = 'border-box';
}
}
var rc = document.getElementById('right_container');
if (document.defaultView.getComputedStyle(rc).overflow == 'scroll') {
rc.style.boxSizing = 'border-box';
rc.style.borderBottom = 'transparent solid 16px';
if ('WebkitBoxSizing' in rc.style) {
rc.style.WebkitBoxSizing = 'border-box';
}
}
addStyle([
'.ScrollooThumb__ {',
' background-color: #B1D9FF !important; ',
'}'
].join('\n'));
var lcScroll = new Scrolloo(document.getElementById('subs_container'));
var rcScroll = new Scrolloo(document.getElementById('right_container'));
//lcScroll.scrollbars.setPosition('BL');
ns.lcScroll = lcScroll;
ns.rcScroll = rcScroll;
window.addEventListener('mousewheel', function(e) {
e.preventDefault();
}, false);
if (Scrolloo.ROOT_SCROLLBAR) {
Scrolloo.ROOT_SCROLLBAR.detach(true);
}
}
}
(function ready(func) {
if (document.getElementsByTagName('body')[0]) func();
else window.addEventListener('load', func, false);
})(function() {
setTimeout(function() { init(); }, 0);
});
})();
// ==UserScript==
// @name scrolloo plugin - pre
// @version 1.00
// @include *
// ==/UserScript==
(function() {
function init() {
var ev = document.createEvent('Event');
ev.initEvent('ScrollooAttach', true, false);
ev.data = 'pre';
document.dispatchEvent(ev);
}
(function ready(func) {
if (document.getElementsByTagName('body')[0]) func();
else window.addEventListener('load', func, false);
})(function() {
setTimeout(function() { init(); }, 0);
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment