Created
June 9, 2012 13:07
-
-
Save tsmallfield/2900918 to your computer and use it in GitHub Desktop.
parallax
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @namespace pera1 | |
*/ | |
;(function(win, doc) { | |
"use strict"; | |
///////////////////////////////////////////////// | |
/** | |
* @enum {function(n:uint):Array.<Number>} | |
*/ | |
var TransitionType = (function() { | |
/*-------------------------------------------- | |
PUBLIC | |
--------------------------------------------*/ | |
function LINEAR(n) { | |
var i = 0, | |
x = 0, | |
dx = 1 / n, | |
ret = []; | |
for (; i < n; ++i) { | |
ret[i] = x; | |
x += dx; | |
} | |
ret[n - 1] = 1; | |
return ret; | |
} | |
function EASE_IN_OUT(n) { | |
var M = Math, | |
PI = M.PI, | |
f = M.sin, | |
x = -PI / 2, | |
dx = PI / n, | |
i = 0, | |
ret = [], | |
y; | |
for (; i < n; ++i) { | |
y = (f(x) + 1) / 2; | |
ret[i] = y > 1 ? 1 : y; | |
x += dx; | |
} | |
ret[n - 1] = 1; | |
return ret; | |
} | |
function EASE_IN(n) { | |
var x = 0, | |
dx = 1 / n, | |
i = 0, | |
ret = [], | |
y; | |
function f(x) { | |
return x * x; | |
} | |
for (; i < n; ++i) { | |
y = f(x); | |
ret[i] = y > 1 ? 1 : y; | |
x += dx; | |
} | |
ret[n - 1] = 1; | |
return ret; | |
} | |
function EASE_OUT(n) { | |
var M = Math, | |
max = M.PI / 2, | |
f = M.sin, | |
x = 0, | |
dx = max / n, | |
i = 0, | |
ret = [], y; | |
for (; i < n; ++i) { | |
y = f(x); | |
ret[i] = y > 1 ? 1 : y; | |
x += dx; | |
} | |
ret[n - 1] = 1; | |
return ret; | |
} | |
/*-------------------------------------------- | |
EXPORT | |
--------------------------------------------*/ | |
return { | |
LINEAR : LINEAR, | |
EASE_IN : EASE_IN, | |
EASE_OUT : EASE_OUT, | |
EASE_IN_OUT : EASE_IN_OUT | |
}; | |
}()); | |
///////////////////////////////////////////////// | |
/** | |
* @param {uint} throttle | |
*/ | |
function Throttle(interval) { | |
var t0 = +new Date, | |
tid; | |
function exec(fnc) { | |
var t1 = +new Date, | |
dt = t1 - t0; | |
clearTimeout(tid); | |
if (dt < interval) { | |
tid = setTimeout(function() { | |
exec(fnc); | |
}, interval - dt); | |
} else { | |
fnc(); | |
t0 = t1; | |
} | |
} | |
return { | |
exec : exec | |
}; | |
} | |
///////////////////////////////////////////////// | |
/** | |
* @enum {string} | |
*/ | |
var MouseEvent = (function(isSmartPhone , isFirefox) { | |
return { | |
CLICK : isSmartPhone ? "click" : "click", | |
MOUSE_DOWN : isSmartPhone ? "touchstart" : "mousedown", | |
MOUSE_MOVE : isSmartPhone ? "touchmove" : "mousemove", | |
MOUSE_UP : isSmartPhone ? "touchend" : "mouseup", | |
MOUSE_OVER : isSmartPhone ? "mouseover" : "mouseover", | |
MOUSE_OUT : isSmartPhone ? "mouseout" : "mouseout", | |
MOUSE_WHEEL : isFirefox ? "DOMMouseScroll" : "mousewheel" | |
}; | |
}("ontouchstart" in win, /firefox/i.test(navigator.userAgent))); | |
///////////////////////////////////////////////// | |
/** | |
* EventDispatcher | |
* | |
* @constructor | |
*/ | |
function EventDispatcher() {} | |
EventDispatcher.prototype.enableThrottle = enableThrottle; | |
EventDispatcher.prototype.disableThrottle = disableThrottle; | |
EventDispatcher.prototype.dispatchEvent = dispatchEvent; | |
EventDispatcher.prototype.hasEventListener = hasEventListener; | |
EventDispatcher.prototype.addEventListener = addEventListener; | |
EventDispatcher.prototype.removeEventListener = removeEventListener; | |
/** | |
* @param {uint} | |
*/ | |
function enableThrottle(msec) { | |
if (msec) { | |
this.throttleInterval = msec; | |
this.throttles = {}; | |
} | |
} | |
function disableThrottle() { | |
delete this.throttleInterval; | |
delete this.throttles; | |
} | |
/** | |
* @this {EventDispatcher} | |
* @param {string} typ | |
* @param {?Object} opt_evt = Object.<string> | |
* @return {undefined} | |
*/ | |
function dispatchEvent(typ, opt_evt) { | |
var self = this, | |
throttles = this.throttles, | |
throttle; | |
if (!typ) { | |
throw "INVALID EVENT TYPE " + typ; | |
} | |
// イベントの発火間隔を間引く | |
if (throttles) { | |
throttle = throttles[typ] || (throttles[typ] = new Throttle(this.throttleInterval)); | |
throttle.exec(dispatch); | |
} else { | |
dispatch(); | |
} | |
function dispatch() { | |
var obj = self.handlers || (self.handlers = {}), | |
arr = [].concat(obj[typ] || []), | |
evt = opt_evt || {}, | |
len, i, fnc; | |
evt.type || (evt.type = typ); | |
// handle specified event type | |
for (i = 0, len = arr.length; i < len; i++) { | |
(fnc = arr[i]) && fnc.call(this, evt); | |
} | |
// handle wildcard "*" event | |
arr = [].concat(obj["*"] || []); | |
for (i = 0, len = arr.length; i < len; i++) { | |
(fnc = arr[i]) && fnc.call(this, evt); | |
} | |
} | |
} | |
/** | |
* @this {EventDispatcher} | |
* @param {string} typ | |
* @param {function(evt:object):undefined} fnc | |
* @return {boolean} | |
*/ | |
function hasEventListener(typ, fnc) { | |
if (!typ) { | |
throw "hasEventListener:INVALID EVENT TYPE " + typ + " " + fn; | |
} | |
var obj = this.handlers || (this.handlers = {}), | |
arr = obj[typ] || [], | |
i = arr.length; | |
while (i) { | |
if (arr[--i] === fnc) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* @this {EventDispatcher} | |
* @param {string} typ | |
* @param {function(evt:Object):undefined} fnc | |
* @return {undefined} | |
*/ | |
function addEventListener(typ, fnc) { | |
if (!typ) { | |
throw "addEventListener:INVALID EVENT TYPE " + typ + " " + fnc; | |
} | |
if (this.hasEventListener(typ, fnc)) { | |
return; | |
} | |
var obj = this.handlers || (this.handlers = {}); | |
( obj[typ] || (obj[typ] = []) ).push(fnc); | |
} | |
/** | |
* @this {EventDispatcher} | |
* @param {string} typ | |
* @param {function(evt:Object):undefined} fnc | |
* @return {undefined} | |
*/ | |
function removeEventListener(typ, fnc) { | |
if (!typ) { | |
throw "removeEventListener:INVALID EVENT TYPE " + typ + " " + fn; | |
} | |
var obj = this.handlers || (this.handlers = {}), | |
arr = obj[typ] || [], | |
i = arr.length; | |
while (i) { | |
arr[--i] === fnc && arr.splice(i, 1); | |
} | |
} | |
///////////////////////////////////////////////// | |
/** | |
* @param {uint} | |
* @return {EventDispatcher} | |
*/ | |
function ScrollManager() { | |
var INTERVAL = 250, | |
ed = new EventDispatcher, | |
html = doc.documentElement, | |
body = doc.body, | |
isInRange = false, | |
wasInRange = false, | |
scrolling = false, | |
cachedPercent = null, | |
cachedDirection = null, | |
start = null, | |
end = null, | |
easingArray, | |
scrollEndTimerId, | |
self; | |
/*-------------------------------------------- | |
INIT | |
--------------------------------------------*/ | |
win.addEventListener ? | |
win.addEventListener("scroll", handleScroll, false) : | |
win.attachEvent("onscroll", handleScroll); | |
easing("linear"); | |
/*-------------------------------------------- | |
PRIVATE | |
--------------------------------------------*/ | |
function createEvent() { | |
var scrollHeight, scrollTop, rate, percent, deltaPercent, direction; | |
if (start === null) { | |
start = 0; | |
} | |
if (end === null) { | |
end = (html.scrollHeight || body.scrollHeight) - (win.innerHeight || html.clientHeight); | |
} | |
if (scrollTop < start) { | |
isInRange = false; | |
rate = 0; | |
percent = 0; | |
direction = 0; | |
} else if (scrollTop > end) { | |
isInRange = false; | |
rate = 1; | |
percent = 100; | |
direction = 0; | |
} else { | |
isInRange = true; | |
scrollHeight = end - start; | |
scrollTop = html.scrollTop || body.scrollTop; | |
rate = scrollTop / scrollHeight; | |
scrollTop = scrollTop - start; | |
rate = scrollTop / (end - start),//scrollHeight; | |
rate = rate < 0 ? 0 : | |
rate > 1 ? 1 : | |
rate; | |
rate = easingArray[rate * (ScrollManager.RESOLUTION - 1) | 0]; | |
percent = rate * 100; | |
deltaPercent = percent - cachedPercent; | |
direction = deltaPercent >= 0 ? ScrollManager.SCROLL_DOWN : ScrollManager.SCROLL_UP; | |
} | |
return { | |
rate : rate, | |
percent : percent, // 画面全体のスクロール率 | |
direction : direction // スクロール方向 (1 | -1) | |
}; | |
} | |
function handleScroll() { | |
var evt = createEvent(), | |
per = evt.percent, | |
dir = evt.direction; | |
if (!scrolling) { | |
triggerScrollStart(); | |
scrolling = true; | |
} | |
if (cachedDirection !== dir) { | |
triggerDirectionChange(evt); | |
cachedDirection = dir; | |
} | |
if (cachedPercent !== per) { | |
triggerScrollUpdate(evt); | |
if (dir > 0) { | |
triggerScrollUp(evt); | |
} else if (dir < 0) { | |
triggerScrollDown(evt); | |
} | |
cachedPercent = per; | |
} | |
if (wasInRange && !isInRange) { | |
ed.dispatchEvent(ScrollManager.OUT_OF_RANGE); | |
} else if (!wasInRange && isInRange) { | |
ed.dispatchEvent(ScrollManager.IN_RANGE); | |
} | |
wasInRange = isInRange; | |
clearTimeout(scrollEndTimerId); | |
scrollEndTimerId = setTimeout(triggerScrollEnd, INTERVAL); | |
} | |
function triggerDirectionChange(evt) { | |
var f = self.onchange; | |
evt = evt || createEvent(); | |
f && f(evt); | |
ed.dispatchEvent(ScrollManager.DIRECTION_CHANGE, evt); | |
} | |
function triggerScrollUpdate(evt) { | |
var f = self.onscroll; | |
evt = evt || createEvent(); | |
f && f(evt); | |
ed.dispatchEvent(ScrollManager.SCROLL_UPDATE, evt); | |
} | |
function triggerScrollStart(evt) { | |
var f = self.onstart; | |
evt = evt || createEvent(); | |
f && f(evt); | |
ed.dispatchEvent(ScrollManager.SCROLL_START, evt); | |
} | |
function triggerScrollEnd(evt) { | |
var f = self.onend; | |
evt = evt || createEvent(); | |
f && f(evt); | |
scrolling = false; | |
cachedDirection = null; | |
cachedPercent = evt.precent; | |
ed.dispatchEvent(ScrollManager.SCROLL_END, evt); | |
} | |
function triggerScrollUp(evt) { | |
var f = self.onup; | |
evt = evt || createEvent(); | |
f && f(evt); | |
ed.dispatchEvent(ScrollManager.SCROLL_UP, evt); | |
} | |
function triggerScrollDown(evt) { | |
var f = self.ondown; | |
evt = evt || createEvent(); | |
f && f(evt); | |
ed.dispatchEvent(ScrollManager.SCROLL_DOWN, evt); | |
} | |
function refresh() { | |
var evt = createEvent(), | |
per = evt.percent; | |
triggerScrollUpdate(evt); | |
cachedPercent = per; | |
} | |
/*------------------------------------------- | |
PUBLIC | |
-------------------------------------------*/ | |
function from(num) { | |
num = +num; | |
if (num < 0) { | |
num = 0; | |
} | |
start = num; | |
ScrollManager.updateMax(num); | |
return self; | |
} | |
function to(num) { | |
num = +num; | |
end = num; | |
ScrollManager.updateMax(num); | |
return self; | |
} | |
function start() { | |
ed.addEventListener(ScrollManager.SCROLL_START, fnc); | |
return self; | |
} | |
function end() { | |
ed.addEventListener(ScrollManager.SCROLL_END, fnc); | |
return self; | |
} | |
function easing(type) { | |
var fnc; | |
type = type.toLowerCase(); | |
switch (type) { | |
case "linear" : | |
fnc = TransitionType.LINEAR; | |
break; | |
case "easeinout" : | |
fnc = TransitionType.EASE_IN_OUT; | |
break; | |
case "easein" : | |
fnc = TransitionType.EASE_IN; | |
break; | |
case "easeout" : | |
fnc = TransitionType.EASE_OUT; | |
break; | |
default : | |
fnc = TransitionType.LINEAR; | |
break; | |
} | |
easingArray = fnc(ScrollManager.RESOLUTION); | |
return self; | |
} | |
function exec(fnc, ctx) { | |
ed.addEventListener(ScrollManager.SCROLL_UPDATE, function() { | |
fnc.apply(ctx || null, arguments); | |
}); | |
refresh(); | |
return self; | |
} | |
function throttle(msec) { | |
if (msec) { | |
ed.enableThrottle(msec); | |
} else { | |
ed.disableThrottle(); | |
} | |
return self; | |
} | |
/*------------------------------------------- | |
EXPORT | |
-------------------------------------------*/ | |
return self = { | |
from : from, | |
to : to, | |
exec : exec, | |
easing : easing, | |
throttle : throttle, | |
onstart : null, | |
onend : null, | |
onup : null, | |
ondown : null, | |
onchange : null, | |
onscroll : null | |
}; | |
} | |
ScrollManager.updateMax = (function() { | |
var max = 0, | |
sty = doc.body.appendChild(doc.createElement("div")).style; | |
sty.position = "static"; | |
sty.visibility = "hidden"; | |
return function(num) { | |
if (num > max) { | |
max = num; | |
sty.height = (num + (win.innerHeight || html.clientHeight)) + "px"; | |
} | |
}; | |
}()); | |
ScrollManager.RESOLUTION = 10000; | |
ScrollManager.SCROLL_UP = -1; | |
ScrollManager.SCROLL_DOWN = 1; | |
ScrollManager.SCROLL_UPDATE = "scrollUpdate"; | |
ScrollManager.SCROLL_START = "scrollStart"; | |
ScrollManager.SCROLL_END = "scrollEnd"; | |
ScrollManager.DIRECTION_CHANGE = "directionChange"; | |
ScrollManager.OUT_OF_RANGE = "outOfRange"; | |
ScrollManager.IN_RANGE = "inRange"; | |
///////////////////////////////////////////////// | |
var pera1 = (function() { | |
function isEasingType(arg) { | |
arg = arg + ""; | |
arg = arg.toLowerCase(); | |
return arg === "linear" || | |
arg === "easeinout" || | |
arg === "easein" || | |
arg === "easeout"; | |
} | |
function misc(/* start, end, callback, easing */) { | |
var sm = new ScrollManager, | |
args = [].slice.call(arguments), | |
i, len, arg; | |
// search for end | |
for (i = args.length; i--;) { | |
arg = args[i]; | |
if (typeof arg === "number") { | |
sm.to(arg); | |
args.splice(i, 1); | |
break; | |
} | |
} | |
// search for start | |
for (i = args.length; i--;) { | |
arg = args[i]; | |
if (typeof arg === "number") { | |
sm.from(arg); | |
args.splice(i, 1); | |
break; | |
} | |
} | |
// search for callback | |
for (i = args.length; i--;) { | |
arg = args[i]; | |
if (typeof arg === "function") { | |
sm.exec(arg, this); | |
args.splice(i, 1); | |
break; | |
} | |
} | |
// search for easingType | |
for (i = args.length; i--;) { | |
arg = args[i]; | |
if (isEasingType(arg)) { | |
sm.easing(arg); | |
args.splice(i, 1); | |
break; | |
} | |
} | |
return sm; | |
} | |
return misc; | |
}()); | |
///////////////////////////////////////////////// | |
// Export | |
$.pera1 = pera1; | |
$.fn.pera1 = pera1; | |
}(this, document)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment