plugins-scroll.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. /*
  2. Plugin Name: BrowserSelector
  3. Written by: Crivos - (http://www.crivos.com)
  4. Version: 0.1
  5. */
  6. (function($) {
  7. $.extend({
  8. browserSelector: function() {
  9. var u = navigator.userAgent,
  10. ua = u.toLowerCase(),
  11. is = function (t) {
  12. return ua.indexOf(t) > -1;
  13. },
  14. g = 'gecko',
  15. w = 'webkit',
  16. s = 'safari',
  17. o = 'opera',
  18. h = document.documentElement,
  19. b = [(!(/opera|webtv/i.test(ua)) && /msie\s(\d)/.test(ua)) ? ('ie ie' + parseFloat(navigator.appVersion.split("MSIE")[1])) : is('firefox/2') ? g + ' ff2' : is('firefox/3.5') ? g + ' ff3 ff3_5' : is('firefox/3') ? g + ' ff3' : is('gecko/') ? g : is('opera') ? o + (/version\/(\d+)/.test(ua) ? ' ' + o + RegExp.jQuery1 : (/opera(\s|\/)(\d+)/.test(ua) ? ' ' + o + RegExp.jQuery2 : '')) : is('konqueror') ? 'konqueror' : is('chrome') ? w + ' chrome' : is('iron') ? w + ' iron' : is('applewebkit/') ? w + ' ' + s + (/version\/(\d+)/.test(ua) ? ' ' + s + RegExp.jQuery1 : '') : is('mozilla/') ? g : '', is('j2me') ? 'mobile' : is('iphone') ? 'iphone' : is('ipod') ? 'ipod' : is('mac') ? 'mac' : is('darwin') ? 'mac' : is('webtv') ? 'webtv' : is('win') ? 'win' : is('freebsd') ? 'freebsd' : (is('x11') || is('linux')) ? 'linux' : '', 'js'];
  20. c = b.join(' ');
  21. h.className += ' ' + c;
  22. }
  23. });
  24. })(jQuery);
  25. /*
  26. Plugin Name: smoothScroll for jQuery.
  27. Written by: Crivos - (http://www.crivos.com)
  28. Version: 0.1
  29. Based on:
  30. SmoothScroll v1.2.1
  31. Licensed under the terms of the MIT license.
  32. People involved
  33. - Balazs Galambosi (maintainer)
  34. - Patrick Brunner (original idea)
  35. - Michael Herf (Pulse Algorithm)
  36. */
  37. (function($) {
  38. $.extend({
  39. smoothScroll: function() {
  40. // Scroll Variables (tweakable)
  41. var defaultOptions = {
  42. // Scrolling Core
  43. frameRate : 150, // [Hz]
  44. animationTime : 700, // [px]
  45. stepSize : 80, // [px]
  46. // Pulse (less tweakable)
  47. // ratio of "tail" to "acceleration"
  48. pulseAlgorithm : true,
  49. pulseScale : 8,
  50. pulseNormalize : 1,
  51. // Acceleration
  52. accelerationDelta : 20, // 20
  53. accelerationMax : 1, // 1
  54. // Keyboard Settings
  55. keyboardSupport : true, // option
  56. arrowScroll : 50, // [px]
  57. // Other
  58. touchpadSupport : true,
  59. fixedBackground : true,
  60. excluded : ""
  61. };
  62. var options = defaultOptions;
  63. // Other Variables
  64. var isExcluded = false;
  65. var isFrame = false;
  66. var direction = { x: 0, y: 0 };
  67. var initDone = false;
  68. var root = document.documentElement;
  69. var activeElement;
  70. var observer;
  71. var deltaBuffer = [ 120, 120, 120 ];
  72. var key = { left: 37, up: 38, right: 39, down: 40, spacebar: 32,
  73. pageup: 33, pagedown: 34, end: 35, home: 36 };
  74. /***********************************************
  75. * INITIALIZE
  76. ***********************************************/
  77. /**
  78. * Tests if smooth scrolling is allowed. Shuts down everything if not.
  79. */
  80. function initTest() {
  81. var disableKeyboard = false;
  82. // disable keys for google reader (spacebar conflict)
  83. if (document.URL.indexOf("google.com/reader/view") > -1) {
  84. disableKeyboard = true;
  85. }
  86. // disable everything if the page is blacklisted
  87. if (options.excluded) {
  88. var domains = options.excluded.split(/[,\n] ?/);
  89. domains.push("mail.google.com"); // exclude Gmail for now
  90. for (var i = domains.length; i--;) {
  91. if (document.URL.indexOf(domains[i]) > -1) {
  92. observer && observer.disconnect();
  93. removeEvent("mousewheel", wheel);
  94. disableKeyboard = true;
  95. isExcluded = true;
  96. break;
  97. }
  98. }
  99. }
  100. // disable keyboard support if anything above requested it
  101. if (disableKeyboard) {
  102. removeEvent("keydown", keydown);
  103. }
  104. if (options.keyboardSupport && !disableKeyboard) {
  105. addEvent("keydown", keydown);
  106. }
  107. }
  108. /**
  109. * Sets up scrolls array, determines if frames are involved.
  110. */
  111. function init() {
  112. if (!document.body) return;
  113. var body = document.body;
  114. var html = document.documentElement;
  115. var windowHeight = window.innerHeight;
  116. var scrollHeight = body.scrollHeight;
  117. // check compat mode for root element
  118. root = (document.compatMode.indexOf('CSS') >= 0) ? html : body;
  119. activeElement = body;
  120. initTest();
  121. initDone = true;
  122. // Checks if this script is running in a frame
  123. if (top != self) {
  124. isFrame = true;
  125. }
  126. /**
  127. * This fixes a bug where the areas left and right to
  128. * the content does not trigger the onmousewheel event
  129. * on some pages. e.g.: html, body { height: 100% }
  130. */
  131. else if (scrollHeight > windowHeight &&
  132. (body.offsetHeight <= windowHeight ||
  133. html.offsetHeight <= windowHeight)) {
  134. // DOMChange (throttle): fix height
  135. var pending = false;
  136. var refresh = function () {
  137. if (!pending && html.scrollHeight != document.height) {
  138. pending = true; // add a new pending action
  139. setTimeout(function () {
  140. html.style.height = document.height + 'px';
  141. pending = false;
  142. }, 500); // act rarely to stay fast
  143. }
  144. };
  145. html.style.height = 'auto';
  146. setTimeout(refresh, 10);
  147. var config = {
  148. attributes: true,
  149. childList: true,
  150. characterData: false
  151. };
  152. observer = new MutationObserver(refresh);
  153. observer.observe(body, config);
  154. // clearfix
  155. if (root.offsetHeight <= windowHeight) {
  156. var underlay = document.createElement("div");
  157. underlay.style.clear = "both";
  158. body.appendChild(underlay);
  159. }
  160. }
  161. // gmail performance fix
  162. if (document.URL.indexOf("mail.google.com") > -1) {
  163. var s = document.createElement("style");
  164. s.innerHTML = ".iu { visibility: hidden }";
  165. (document.getElementsByTagName("head")[0] || html).appendChild(s);
  166. }
  167. // facebook better home timeline performance
  168. // all the HTML resized images make rendering CPU intensive
  169. else if (document.URL.indexOf("www.facebook.com") > -1) {
  170. var home_stream = document.getElementById("home_stream");
  171. home_stream && (home_stream.style.webkitTransform = "translateZ(0)");
  172. }
  173. // disable fixed background
  174. if (!options.fixedBackground && !isExcluded) {
  175. body.style.backgroundAttachment = "scroll";
  176. html.style.backgroundAttachment = "scroll";
  177. }
  178. }
  179. /************************************************
  180. * SCROLLING
  181. ************************************************/
  182. var que = [];
  183. var pending = false;
  184. var lastScroll = +new Date;
  185. /**
  186. * Pushes scroll actions to the scrolling queue.
  187. */
  188. function scrollArray(elem, left, top, delay) {
  189. delay || (delay = 1000);
  190. directionCheck(left, top);
  191. if (options.accelerationMax != 1) {
  192. var now = +new Date;
  193. var elapsed = now - lastScroll;
  194. if (elapsed < options.accelerationDelta) {
  195. var factor = (1 + (30 / elapsed)) / 2;
  196. if (factor > 1) {
  197. factor = Math.min(factor, options.accelerationMax);
  198. left *= factor;
  199. top *= factor;
  200. }
  201. }
  202. lastScroll = +new Date;
  203. }
  204. // push a scroll command
  205. que.push({
  206. x: left,
  207. y: top,
  208. lastX: (left < 0) ? 0.99 : -0.99,
  209. lastY: (top < 0) ? 0.99 : -0.99,
  210. start: +new Date
  211. });
  212. // don't act if there's a pending queue
  213. if (pending) {
  214. return;
  215. }
  216. var scrollWindow = (elem === document.body);
  217. var step = function (time) {
  218. var now = +new Date;
  219. var scrollX = 0;
  220. var scrollY = 0;
  221. for (var i = 0; i < que.length; i++) {
  222. var item = que[i];
  223. var elapsed = now - item.start;
  224. var finished = (elapsed >= options.animationTime);
  225. // scroll position: [0, 1]
  226. var position = (finished) ? 1 : elapsed / options.animationTime;
  227. // easing [optional]
  228. if (options.pulseAlgorithm) {
  229. position = pulse(position);
  230. }
  231. // only need the difference
  232. var x = (item.x * position - item.lastX) >> 0;
  233. var y = (item.y * position - item.lastY) >> 0;
  234. // add this to the total scrolling
  235. scrollX += x;
  236. scrollY += y;
  237. // update last values
  238. item.lastX += x;
  239. item.lastY += y;
  240. // delete and step back if it's over
  241. if (finished) {
  242. que.splice(i, 1); i--;
  243. }
  244. }
  245. // scroll left and top
  246. if (scrollWindow) {
  247. window.scrollBy(scrollX, scrollY);
  248. }
  249. else {
  250. if (scrollX) elem.scrollLeft += scrollX;
  251. if (scrollY) elem.scrollTop += scrollY;
  252. }
  253. // clean up if there's nothing left to do
  254. if (!left && !top) {
  255. que = [];
  256. }
  257. if (que.length) {
  258. requestFrame(step, elem, (delay / options.frameRate + 1));
  259. } else {
  260. pending = false;
  261. }
  262. };
  263. // start a new queue of actions
  264. requestFrame(step, elem, 0);
  265. pending = true;
  266. }
  267. /***********************************************
  268. * EVENTS
  269. ***********************************************/
  270. /**
  271. * Mouse wheel handler.
  272. * @param {Object} event
  273. */
  274. function wheel(event) {
  275. if (!initDone) {
  276. init();
  277. }
  278. var target = event.target;
  279. var overflowing = overflowingAncestor(target);
  280. // use default if there's no overflowing
  281. // element or default action is prevented
  282. if (!overflowing || event.defaultPrevented ||
  283. isNodeName(activeElement, "embed") ||
  284. (isNodeName(target, "embed") && /\.pdf/i.test(target.src))) {
  285. return true;
  286. }
  287. var deltaX = event.wheelDeltaX || 0;
  288. var deltaY = event.wheelDeltaY || 0;
  289. // use wheelDelta if deltaX/Y is not available
  290. if (!deltaX && !deltaY) {
  291. deltaY = event.wheelDelta || 0;
  292. }
  293. // check if it's a touchpad scroll that should be ignored
  294. if (!options.touchpadSupport && isTouchpad(deltaY)) {
  295. return true;
  296. }
  297. // scale by step size
  298. // delta is 120 most of the time
  299. // synaptics seems to send 1 sometimes
  300. if (Math.abs(deltaX) > 1.2) {
  301. deltaX *= options.stepSize / 120;
  302. }
  303. if (Math.abs(deltaY) > 1.2) {
  304. deltaY *= options.stepSize / 120;
  305. }
  306. scrollArray(overflowing, -deltaX, -deltaY);
  307. event.preventDefault();
  308. }
  309. /**
  310. * Keydown event handler.
  311. * @param {Object} event
  312. */
  313. function keydown(event) {
  314. var target = event.target;
  315. var modifier = event.ctrlKey || event.altKey || event.metaKey ||
  316. (event.shiftKey && event.keyCode !== key.spacebar);
  317. // do nothing if user is editing text
  318. // or using a modifier key (except shift)
  319. // or in a dropdown
  320. if ( /input|textarea|select|embed/i.test(target.nodeName) ||
  321. target.isContentEditable ||
  322. event.defaultPrevented ||
  323. modifier ) {
  324. return true;
  325. }
  326. // spacebar should trigger button press
  327. if (isNodeName(target, "button") &&
  328. event.keyCode === key.spacebar) {
  329. return true;
  330. }
  331. var shift, x = 0, y = 0;
  332. var elem = overflowingAncestor(activeElement);
  333. var clientHeight = elem.clientHeight;
  334. if (elem == document.body) {
  335. clientHeight = window.innerHeight;
  336. }
  337. switch (event.keyCode) {
  338. case key.up:
  339. y = -options.arrowScroll;
  340. break;
  341. case key.down:
  342. y = options.arrowScroll;
  343. break;
  344. case key.spacebar: // (+ shift)
  345. shift = event.shiftKey ? 1 : -1;
  346. y = -shift * clientHeight * 0.9;
  347. break;
  348. case key.pageup:
  349. y = -clientHeight * 0.9;
  350. break;
  351. case key.pagedown:
  352. y = clientHeight * 0.9;
  353. break;
  354. case key.home:
  355. y = -elem.scrollTop;
  356. break;
  357. case key.end:
  358. var damt = elem.scrollHeight - elem.scrollTop - clientHeight;
  359. y = (damt > 0) ? damt+10 : 0;
  360. break;
  361. case key.left:
  362. x = -options.arrowScroll;
  363. break;
  364. case key.right:
  365. x = options.arrowScroll;
  366. break;
  367. default:
  368. return true; // a key we don't care about
  369. }
  370. scrollArray(elem, x, y);
  371. event.preventDefault();
  372. }
  373. /**
  374. * Mousedown event only for updating activeElement
  375. */
  376. function mousedown(event) {
  377. activeElement = event.target;
  378. }
  379. /***********************************************
  380. * OVERFLOW
  381. ***********************************************/
  382. var cache = {}; // cleared out every once in while
  383. setInterval(function () { cache = {}; }, 10 * 1000);
  384. var uniqueID = (function () {
  385. var i = 0;
  386. return function (el) {
  387. return el.uniqueID || (el.uniqueID = i++);
  388. };
  389. })();
  390. function setCache(elems, overflowing) {
  391. for (var i = elems.length; i--;)
  392. cache[uniqueID(elems[i])] = overflowing;
  393. return overflowing;
  394. }
  395. function overflowingAncestor(el) {
  396. var elems = [];
  397. var rootScrollHeight = root.scrollHeight;
  398. do {
  399. var cached = cache[uniqueID(el)];
  400. if (cached) {
  401. return setCache(elems, cached);
  402. }
  403. elems.push(el);
  404. if (rootScrollHeight === el.scrollHeight) {
  405. if (!isFrame || root.clientHeight + 10 < rootScrollHeight) {
  406. return setCache(elems, document.body); // scrolling root in WebKit
  407. }
  408. } else if (el.clientHeight + 10 < el.scrollHeight) {
  409. overflow = getComputedStyle(el, "").getPropertyValue("overflow-y");
  410. if (overflow === "scroll" || overflow === "auto") {
  411. return setCache(elems, el);
  412. }
  413. }
  414. } while (el = el.parentNode);
  415. }
  416. /***********************************************
  417. * HELPERS
  418. ***********************************************/
  419. function addEvent(type, fn, bubble) {
  420. window.addEventListener(type, fn, (bubble||false));
  421. }
  422. function removeEvent(type, fn, bubble) {
  423. window.removeEventListener(type, fn, (bubble||false));
  424. }
  425. function isNodeName(el, tag) {
  426. return (el.nodeName||"").toLowerCase() === tag.toLowerCase();
  427. }
  428. function directionCheck(x, y) {
  429. x = (x > 0) ? 1 : -1;
  430. y = (y > 0) ? 1 : -1;
  431. if (direction.x !== x || direction.y !== y) {
  432. direction.x = x;
  433. direction.y = y;
  434. que = [];
  435. lastScroll = 0;
  436. }
  437. }
  438. var deltaBufferTimer;
  439. function isTouchpad(deltaY) {
  440. if (!deltaY) return;
  441. deltaY = Math.abs(deltaY)
  442. deltaBuffer.push(deltaY);
  443. deltaBuffer.shift();
  444. clearTimeout(deltaBufferTimer);
  445. var allEquals = (deltaBuffer[0] == deltaBuffer[1] &&
  446. deltaBuffer[1] == deltaBuffer[2]);
  447. var allDivisable = (isDivisible(deltaBuffer[0], 120) &&
  448. isDivisible(deltaBuffer[1], 120) &&
  449. isDivisible(deltaBuffer[2], 120));
  450. return !(allEquals || allDivisable);
  451. }
  452. function isDivisible(n, divisor) {
  453. return (Math.floor(n / divisor) == n / divisor);
  454. }
  455. var requestFrame = (function () {
  456. return window.requestAnimationFrame ||
  457. window.webkitRequestAnimationFrame ||
  458. function (callback, element, delay) {
  459. window.setTimeout(callback, delay || (1000/60));
  460. };
  461. })();
  462. var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
  463. /***********************************************
  464. * PULSE
  465. ***********************************************/
  466. /**
  467. * Viscous fluid with a pulse for part and decay for the rest.
  468. * - Applies a fixed force over an interval (a damped acceleration), and
  469. * - Lets the exponential bleed away the velocity over a longer interval
  470. * - Michael Herf, http://stereopsis.com/stopping/
  471. */
  472. function pulse_(x) {
  473. var val, start, expx;
  474. // test
  475. x = x * options.pulseScale;
  476. if (x < 1) { // acceleartion
  477. val = x - (1 - Math.exp(-x));
  478. } else { // tail
  479. // the previous animation ended here:
  480. start = Math.exp(-1);
  481. // simple viscous drag
  482. x -= 1;
  483. expx = 1 - Math.exp(-x);
  484. val = start + (expx * (1 - start));
  485. }
  486. return val * options.pulseNormalize;
  487. }
  488. function pulse(x) {
  489. if (x >= 1) return 1;
  490. if (x <= 0) return 0;
  491. if (options.pulseNormalize == 1) {
  492. options.pulseNormalize /= pulse_(1);
  493. }
  494. return pulse_(x);
  495. }
  496. addEvent("mousedown", mousedown);
  497. addEvent("mousewheel", wheel);
  498. addEvent("load", init);
  499. }
  500. });
  501. })(jQuery);