Skip to content

Instantly share code, notes, and snippets.

@tsmallfield
Created June 9, 2012 13:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tsmallfield/2900918 to your computer and use it in GitHub Desktop.
Save tsmallfield/2900918 to your computer and use it in GitHub Desktop.
parallax
/**
* @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