Skip to content
Browse files

Cache the offsetHeight and offsetTop as properties of the list items …

…instead of using a WeakMap. This won't leak memory as we only cache numeric values.
  • Loading branch information...
1 parent 0610c33 commit f69235784123c780705742c879cc6e2542ad64d4 @fgnass committed Apr 15, 2012
Showing with 39 additions and 508 deletions.
  1. +11 −12 index.html
  2. +0 −451 js/lib/WeakMap.js
  3. +28 −45 js/stroll.js
View
23 index.html
@@ -1,34 +1,34 @@
-<!doctype html>
+<!doctype html>
<html lang="en">
-
+
<head>
<meta charset="utf-8">
-
+
<title>CSS Scroll Effects</title>
<meta name="description" content="A set of list scroll effects applied via CSS.">
<meta name="author" content="Hakim El Hattab">
-
+
<link href='http://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
-
+
<link rel="stylesheet" href="css/reset.css">
<link rel="stylesheet" href="css/main.css">
</head>
-
+
<body>
<header>
Silly list scroll effects. <small>Created by <a href="http://twitter.com/hakimel">@hakimel</a>. Tested in WebKit/FF Nightly. In the mood for more CSS? <a href="http://hakim.se/experiments">Here you go</a>.</small>
<div>
<a href="http://twitter.com/share" class="twitter-share-button" data-text="CSS3 list scroll effects created by @hakimel" data-url="http://lab.hakim.se/scroll-effects" data-count="small" data-related="hakimel"></a>
-
- <iframe id="facebook-button" src="http://www.facebook.com/plugins/like.php?href=http%3A%2F%2Flab.hakim.se%2Fscroll-effects%2F&layout=button_count&show_faces=false&width=93&action=like&font=arial&colorscheme=light&height=21" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:85px; height:24px; position: relative; top: 4px;" allowTransparency="true"></iframe>
+
+ <iframe id="facebook-button" src="http://www.facebook.com/plugins/like.php?href=http%3A%2F%2Flab.hakim.se%2Fscroll-effects%2F&layout=button_count&show_faces=false&width=93&action=like&font=arial&colorscheme=light&height=21" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:85px; height:24px; position: relative; top: 4px;" allowTransparency="true"></iframe>
<script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>
</div>
</header>
-
+
<div id="main">
<article>
<h2>Curl (CSS 3D)</h2>
@@ -68,7 +68,6 @@
</article>
</div>
- <script src="js/lib/WeakMap.js"></script>
<script src="js/stroll.js"></script>
<script>
var lists = document.querySelectorAll( '#main ul' );
@@ -93,6 +92,6 @@
s.parentNode.insertBefore(g, s);
})(document, 'script');
</script>
-
+
</body>
-</html>
+</html>
View
451 js/lib/WeakMap.js
@@ -1,451 +0,0 @@
-// Copyright (C) 2011 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-/**
- * @fileoverview Install a leaky WeakMap emulation on platforms that
- * don't provide a built-in one.
- *
- * <p>Assumes that an ES5 platform where, if {@code WeakMap} is
- * already present, then it conforms to the anticipated ES6
- * specification. To run this file on an ES5 or almost ES5
- * implementation where the {@code WeakMap} specification does not
- * quite conform, run <code>repairES5.js</code> first.
- *
- * @author Mark S. Miller
- * @requires ses, crypto, ArrayBuffer, Uint8Array
- * @overrides WeakMap, WeakMapModule
- */
-
-/**
- * This {@code WeakMap} emulation is observably equivalent to the
- * ES-Harmony WeakMap, but with leakier garbage collection properties.
- *
- * <p>As with true WeakMaps, in this emulation, a key does not
- * retain maps indexed by that key and (crucially) a map does not
- * retain the keys it indexes. A map by itself also does not retain
- * the values associated with that map.
- *
- * <p>However, the values associated with a key in some map are
- * retained so long as that key is retained and those associations are
- * not overridden. For example, when used to support membranes, all
- * values exported from a given membrane will live for the lifetime
- * they would have had in the absence of an interposed membrane. Even
- * when the membrane is revoked, all objects that would have been
- * reachable in the absence of revocation will still be reachable, as
- * far as the GC can tell, even though they will no longer be relevant
- * to ongoing computation.
- *
- * <p>The API implemented here is approximately the API as implemented
- * in FF6.0a1 and agreed to by MarkM, Andreas Gal, and Dave Herman,
- * rather than the offially approved proposal page. TODO(erights):
- * upgrade the ecmascript WeakMap proposal page to explain this API
- * change and present to EcmaScript committee for their approval.
- *
- * <p>The first difference between the emulation here and that in
- * FF6.0a1 is the presence of non enumerable {@code get___, has___,
- * set___, and delete___} methods on WeakMap instances to represent
- * what would be the hidden internal properties of a primitive
- * implementation. Whereas the FF6.0a1 WeakMap.prototype methods
- * require their {@code this} to be a genuine WeakMap instance (i.e.,
- * an object of {@code [[Class]]} "WeakMap}), since there is nothing
- * unforgeable about the pseudo-internal method names used here,
- * nothing prevents these emulated prototype methods from being
- * applied to non-WeakMaps with pseudo-internal methods of the same
- * names.
- *
- * <p>Another difference is that our emulated {@code
- * WeakMap.prototype} is not itself a WeakMap. A problem with the
- * current FF6.0a1 API is that WeakMap.prototype is itself a WeakMap
- * providing ambient mutability and an ambient communications
- * channel. Thus, if a WeakMap is already present and has this
- * problem, repairES5.js wraps it in a safe wrappper in order to
- * prevent access to this channel. (See
- * PATCH_MUTABLE_FROZEN_WEAKMAP_PROTO in repairES5.js).
- */
-var WeakMap;
-
-/**
- * If this is a full <a href=
- * "http://code.google.com/p/es-lab/wiki/SecureableES5"
- * >secureable ES5</a> platform and the ES-Harmony {@code WeakMap} is
- * absent, install an approximate emulation.
- *
- * <p>If this is almost a secureable ES5 platform, then WeakMap.js
- * should be run after repairES5.js.
- *
- * <p>See {@code WeakMap} for documentation of the garbage collection
- * properties of this WeakMap emulation.
- */
-(function WeakMapModule() {
- "use strict";
-
- if (typeof ses !== 'undefined' && ses.ok && !ses.ok()) {
- // already too broken, so give up
- return;
- }
-
- if (typeof WeakMap === 'function') {
- // assumed fine, so we're done.
- return;
- }
-
- var hop = Object.prototype.hasOwnProperty;
- var gopn = Object.getOwnPropertyNames;
- var defProp = Object.defineProperty;
-
- /**
- * Holds the orginal static properties of the Object constructor,
- * after repairES5 fixes these if necessary to be a more complete
- * secureable ES5 environment, but before installing the following
- * WeakMap emulation overrides and before any untrusted code runs.
- */
- var originalProps = {};
- gopn(Object).forEach(function(name) {
- originalProps[name] = Object[name];
- });
-
- /**
- * Security depends on HIDDEN_NAME being both <i>unguessable</i> and
- * <i>undiscoverable</i> by untrusted code.
- *
- * <p>Given the known weaknesses of Math.random() on existing
- * browsers, it does not generate unguessability we can be confident
- * of.
- *
- * <p>It is the monkey patching logic in this file that is intended
- * to ensure undiscoverability. The basic idea is that there are
- * three fundamental means of discovering properties of an object:
- * The for/in loop, Object.keys(), and Object.getOwnPropertyNames(),
- * as well as some proposed ES6 extensions that appear on our
- * whitelist. The first two only discover enumerable properties, and
- * we only use HIDDEN_NAME to name a non-enumerable property, so the
- * only remaining threat should be getOwnPropertyNames and some
- * proposed ES6 extensions that appear on our whitelist. We monkey
- * patch them to remove HIDDEN_NAME from the list of properties they
- * returns.
- *
- * <p>TODO(erights): On a platform with built-in Proxies, proxies
- * could be used to trap and thereby discover the HIDDEN_NAME, so we
- * need to monkey patch Proxy.create, Proxy.createFunction, etc, in
- * order to wrap the provided handler with the real handler which
- * filters out all traps using HIDDEN_NAME.
- *
- * <p>TODO(erights): Revisit Mike Stay's suggestion that we use an
- * encapsulated function at a not-necessarily-secret name, which
- * uses the Stiegler shared-state rights amplification pattern to
- * reveal the associated value only to the WeakMap in which this key
- * is associated with that value. Since only the key retains the
- * function, the function can also remember the key without causing
- * leakage of the key, so this doesn't violate our general gc
- * goals. In addition, because the name need not be a guarded
- * secret, we could efficiently handle cross-frame frozen keys.
- */
- var HIDDEN_NAME = 'ident:' + Math.random() + '___';
-
- if (typeof crypto !== 'undefined' &&
- typeof crypto.getRandomValues === 'function' &&
- typeof ArrayBuffer === 'function' &&
- typeof Uint8Array === 'function') {
- var ab = new ArrayBuffer(25);
- var u8s = new Uint8Array(ab);
- crypto.getRandomValues(u8s);
- HIDDEN_NAME = 'rand:' +
- Array.prototype.map.call(u8s, function(u8) {
- return (u8 % 36).toString(36);
- }).join('') + '___';
- }
-
- /**
- * Monkey patch getOwnPropertyNames to avoid revealing the
- * HIDDEN_NAME.
- *
- * <p>The ES5.1 spec requires each name to appear only once, but as
- * of this writing, this requirement is controversial for ES6, so we
- * made this code robust against this case. If the resulting extra
- * search turns out to be expensive, we can probably relax this once
- * ES6 is adequately supported on all major browsers, iff no browser
- * versions we support at that time have relaxed this constraint
- * without providing built-in ES6 WeakMaps.
- */
- defProp(Object, 'getOwnPropertyNames', {
- value: function fakeGetOwnPropertyNames(obj) {
- return gopn(obj).filter(function(name) {
- return name !== HIDDEN_NAME;
- });
- }
- });
-
- /**
- * getPropertyNames is not in ES5 but it is proposed for ES6 and
- * does appear in our whitelist, so we need to clean it too.
- */
- if ('getPropertyNames' in Object) {
- defProp(Object, 'getPropertyNames', {
- value: function fakeGetPropertyNames(obj) {
- return originalProps.getPropertyNames(obj).filter(function(name) {
- return name !== HIDDEN_NAME;
- });
- }
- });
- }
-
- /**
- * <p>To treat objects as identity-keys with reasonable efficiency
- * on ES5 by itself (i.e., without any object-keyed collections), we
- * need to add a hidden property to such key objects when we
- * can. This raises several issues:
- * <ul>
- * <li>Arranging to add this property to objects before we lose the
- * chance, and
- * <li>Hiding the existence of this new property from most
- * JavaScript code.
- * <li>Preventing <i>certification theft</i>, where one object is
- * created falsely claiming to be the key of an association
- * actually keyed by another object.
- * <li>Preventing <i>value theft</i>, where untrusted code with
- * access to a key object but not a weak map nevertheless
- * obtains access to the value associated with that key in that
- * weak map.
- * </ul>
- * We do so by
- * <ul>
- * <li>Making the name of the hidden property unguessable, so "[]"
- * indexing, which we cannot intercept, cannot be used to access
- * a property without knowing the name.
- * <li>Making the hidden property non-enumerable, so we need not
- * worry about for-in loops or {@code Object.keys},
- * <li>monkey patching those reflective methods that would
- * prevent extensions, to add this hidden property first,
- * <li>monkey patching those methods that would reveal this
- * hidden property.
- * </ul>
- * Unfortunately, because of same-origin iframes, we cannot reliably
- * add this hidden property before an object becomes
- * non-extensible. Instead, if we encounter a non-extensible object
- * without a hidden record that we can detect (whether or not it has
- * a hidden record stored under a name secret to us), then we just
- * use the key object itself to represent its identity in a brute
- * force leaky map stored in the weak map, losing all the advantages
- * of weakness for these.
- */
- function getHiddenRecord(key) {
- if (key !== Object(key)) {
- throw new TypeError('Not an object: ' + key);
- }
- var hiddenRecord = key[HIDDEN_NAME];
- if (hiddenRecord && hiddenRecord.key === key) { return hiddenRecord; }
- if (!originalProps.isExtensible(key)) {
- // Weak map must brute force, as explained in doc-comment above.
- return void 0;
- }
- var gets = [];
- var vals = [];
- hiddenRecord = {
- key: key, // self pointer for quick own check above.
- gets: gets, // get___ methods identifying weak maps
- vals: vals // values associated with this key in each
- // corresponding weak map.
- };
- defProp(key, HIDDEN_NAME, {
- value: hiddenRecord,
- writable: false,
- enumerable: false,
- configurable: false
- });
- return hiddenRecord;
- }
-
-
- /**
- * Monkey patch operations that would make their argument
- * non-extensible.
- *
- * <p>The monkey patched versions throw a TypeError if their
- * argument is not an object, so it should only be done to functions
- * that should throw a TypeError anyway if their argument is not an
- * object.
- */
- (function(){
- var oldFreeze = Object.freeze;
- defProp(Object, 'freeze', {
- value: function identifyingFreeze(obj) {
- getHiddenRecord(obj);
- return oldFreeze(obj);
- }
- });
- var oldSeal = Object.seal;
- defProp(Object, 'seal', {
- value: function identifyingSeal(obj) {
- getHiddenRecord(obj);
- return oldSeal(obj);
- }
- });
- var oldPreventExtensions = Object.preventExtensions;
- defProp(Object, 'preventExtensions', {
- value: function identifyingPreventExtensions(obj) {
- getHiddenRecord(obj);
- return oldPreventExtensions(obj);
- }
- });
- })();
-
-
- function constFunc(func) {
- func.prototype = null;
- return Object.freeze(func);
- }
-
- // Right now (12/25/2012) the histogram supports the current
- // representation. We should check this occasionally, as a true
- // constant time representation is easy.
- // var histogram = [];
-
- WeakMap = function() {
- // We are currently (12/25/2012) never encountering any prematurely
- // non-extensible keys.
- var keys = []; // brute force for prematurely non-extensible keys.
- var vals = []; // brute force for corresponding values.
-
- function get___(key, opt_default) {
- var hr = getHiddenRecord(key);
- var i, vs;
- if (hr) {
- i = hr.gets.indexOf(get___);
- vs = hr.vals;
- } else {
- i = keys.indexOf(key);
- vs = vals;
- }
- return (i >= 0) ? vs[i] : opt_default;
- }
-
- function has___(key) {
- var hr = getHiddenRecord(key);
- var i;
- if (hr) {
- i = hr.gets.indexOf(get___);
- } else {
- i = keys.indexOf(key);
- }
- return i >= 0;
- }
-
- function set___(key, value) {
- var hr = getHiddenRecord(key);
- var i;
- if (hr) {
- i = hr.gets.indexOf(get___);
- if (i >= 0) {
- hr.vals[i] = value;
- } else {
-// i = hr.gets.length;
-// histogram[i] = (histogram[i] || 0) + 1;
- hr.gets.push(get___);
- hr.vals.push(value);
- }
- } else {
- i = keys.indexOf(key);
- if (i >= 0) {
- vals[i] = value;
- } else {
- keys.push(key);
- vals.push(value);
- }
- }
- }
-
- function delete___(key) {
- var hr = getHiddenRecord(key);
- var i;
- if (hr) {
- i = hr.gets.indexOf(get___);
- if (i >= 0) {
- hr.gets.splice(i, 1);
- hr.vals.splice(i, 1);
- }
- } else {
- i = keys.indexOf(key);
- if (i >= 0) {
- keys.splice(i, 1);
- vals.splice(i, 1);
- }
- }
- return true;
- }
-
- return Object.create(WeakMap.prototype, {
- get___: { value: constFunc(get___) },
- has___: { value: constFunc(has___) },
- set___: { value: constFunc(set___) },
- delete___: { value: constFunc(delete___) }
- });
- };
- WeakMap.prototype = Object.create(Object.prototype, {
- get: {
- /**
- * Return the value most recently associated with key, or
- * opt_default if none.
- */
- value: function get(key, opt_default) {
- return this.get___(key, opt_default);
- },
- writable: true,
- configurable: true
- },
-
- has: {
- /**
- * Is there a value associated with key in this WeakMap?
- */
- value: function has(key) {
- return this.has___(key);
- },
- writable: true,
- configurable: true
- },
-
- set: {
- /**
- * Associate value with key in this WeakMap, overwriting any
- * previous association if present.
- */
- value: function set(key, value) {
- this.set___(key, value);
- },
- writable: true,
- configurable: true
- },
-
- 'delete': {
- /**
- * Remove any association for key in this WeakMap, returning
- * whether there was one.
- *
- * <p>Note that the boolean return here does not work like the
- * {@code delete} operator. The {@code delete} operator returns
- * whether the deletion succeeds at bringing about a state in
- * which the deleted property is absent. The {@code delete}
- * operator therefore returns true if the property was already
- * absent, whereas this {@code delete} method returns false if
- * the association was already absent.
- */
- value: function remove(key) {
- return this.delete___(key);
- },
- writable: true,
- configurable: true
- }
- });
-
-})();
View
73 js/stroll.js
@@ -1,6 +1,7 @@
/**
* @author Hakim El Hattab | http://hakim.se
* @author Paul Irish | http://paulirish.com/
+ * @author Felix Gnass | http://fgnass.github.com
*/
window.requestAnimFrame = (function(){
@@ -15,22 +16,19 @@ window.requestAnimFrame = (function(){
})();
-// we're using a WeakMap polyfill to associate elements and data with elements
-// http://code.google.com/p/es-lab/source/browse/trunk/src/ses/WeakMap.js
-var weakmap = new WeakMap();
-
var Stroll = {
bind: function(element) {
- // cache items so we don't look each time.
- var items = element.querySelectorAll( 'li' );
- weakmap.set( element, {
- items : items
- } );
+ var items = Array.prototype.slice.apply(element.children);
// caching some heights so we don't need to go back to the DOM so much
var listHeight = element.offsetHeight;
- var itemHeight = items[0].offsetHeight; // assumes all heights same
+
+ // one loop to get the offsets from the DOM
+ for( var i = 0; i < items.length; i++ ) {
+ items[i]._offsetTop = items[i].offsetTop
+ items[i]._offsetHeight = items[i].offsetHeight
+ }
return (function() {
@@ -42,44 +40,29 @@ var Stroll = {
// apply past/future classes to list items outside of the viewport
function update() {
var scrollTop = element.pageYOffset || element.scrollTop,
- scrollBottom = scrollTop + listHeight;
-
- var elemObj = weakmap.get(element);
+ scrollBottom = scrollTop + listHeight;
// quit if nothing changed.
- if( scrollTop != elemObj.lastTop ) {
- elemObj.lastTop = scrollTop;
-
- var items = elemObj.items;
-
- // one loop to get the offsets from the DOM
- for( var i = 0, len = items.length; i < len; i++ ) {
-
- // this offsetTop call is the perf killer.
- weakmap.set( items[i], {
- offset : items[i].offsetTop
- } );
- }
-
- // one loop to make our changes to the DOM
- for( var j = 0, len = items.length; j < len; j++ ) {
- var item = items[j],
- offsetTop = weakmap.get( item ).offset;
-
- if( offsetTop + itemHeight < scrollTop ) {
- item.classList.add('past');
- }
- else if( offsetTop > scrollBottom ) {
- item.classList.add('future');
- }
- else {
- item.classList.remove('past');
- item.classList.remove('future');
- }
- }
- }
+ if(scrollTop == element.lastTop ) return;
+ element.lastTop = scrollTop;
+
+ // one loop to make our changes to the DOM
+ for( var i = 0, len = items.length; i < len; i++ ) {
+ var item = items[i];
+
+ if( item._offsetTop + item._offsetHeight < scrollTop ) {
+ item.classList.add('past');
+ }
+ else if( item._offsetTop > scrollBottom ) {
+ item.classList.add('future');
+ }
+ else {
+ item.classList.remove('past');
+ item.classList.remove('future');
+ }
+ }
}
})();
}
-};
+};

0 comments on commit f692357

Please sign in to comment.
Something went wrong with that request. Please try again.