Permalink
Browse files

Move all accesses of element dimensions into a new BoundsInfo object,…

… which queries the element dimensions once at the start of an update and caches that value through the update process. This prevents potentially dozens of reflows by the browser and gives a huge performance boost in IE8 particularly.
  • Loading branch information...
1 parent 6b374bb commit 04ed5d5bb3138975e8fa21e72e9041578ec112c3 Jason Johnston committed Sep 1, 2010
View
@@ -24,6 +24,7 @@
<fileset file="${src_dir}/Angle.js" />
<fileset file="${src_dir}/Color.js" />
<fileset file="${src_dir}/Tokenizer.js" />
+ <fileset file="${src_dir}/BoundsInfo.js" />
<fileset file="${src_dir}/StyleInfoBase.js" />
<fileset file="${src_dir}/BackgroundStyleInfo.js" />
<fileset file="${src_dir}/BorderStyleInfo.js" />
@@ -17,8 +17,8 @@ PIE.BackgroundRenderer = PIE.RendererBase.newRenderer( {
isActive: function() {
var si = this.styleInfos,
- el = this.element;
- return el.offsetWidth && el.offsetHeight && (
+ bounds = this.boundsInfo.getBounds();
+ return bounds.w && bounds.h && (
si.borderImageInfo.isActive() ||
si.borderRadiusInfo.isActive() ||
si.backgroundInfo.isActive() ||
@@ -51,6 +51,7 @@ PIE.BackgroundRenderer = PIE.RendererBase.newRenderer( {
*/
drawBgColor: function() {
var props = this.styleInfos.backgroundInfo.getProps(),
+ bounds = this.boundsInfo.getBounds(),
el = this.element,
color = props && props.color,
shape, w, h, s, alpha;
@@ -59,8 +60,8 @@ PIE.BackgroundRenderer = PIE.RendererBase.newRenderer( {
this.hideBackground();
shape = this.getShape( 'bgColor', 'fill', this.getBox(), 1 );
- w = el.offsetWidth;
- h = el.offsetHeight;
+ w = bounds.w;
+ h = bounds.h;
shape.stroked = false;
shape.coordsize = w * 2 + ',' + h * 2;
shape.coordorigin = '1,1';
@@ -84,15 +85,15 @@ PIE.BackgroundRenderer = PIE.RendererBase.newRenderer( {
*/
drawBgImages: function() {
var props = this.styleInfos.backgroundInfo.getProps(),
+ bounds = this.boundsInfo.getBounds(),
images = props && props.images,
- img, el, shape, w, h, s, i;
+ img, shape, w, h, s, i;
if( images ) {
this.hideBackground();
- el = this.element;
- w = el.offsetWidth,
- h = el.offsetHeight,
+ w = bounds.w;
+ h = bounds.h;
i = images.length;
while( i-- ) {
@@ -134,9 +135,9 @@ PIE.BackgroundRenderer = PIE.RendererBase.newRenderer( {
PIE.Util.withImageSize( shape.fill.src, function( size ) {
var fill = shape.fill,
el = this.element,
- elW = el.offsetWidth,
- elH = el.offsetHeight,
- cs = el.currentStyle,
+ bounds = this.boundsInfo.getBounds(),
+ elW = bounds.w,
+ elH = bounds.h,
si = this.styleInfos,
border = si.borderInfo.getProps(),
bw = border && border.widths,
@@ -182,8 +183,9 @@ PIE.BackgroundRenderer = PIE.RendererBase.newRenderer( {
*/
addLinearGradient: function( shape, info ) {
var el = this.element,
- w = el.offsetWidth,
- h = el.offsetHeight,
+ bounds = this.boundsInfo.getBounds(),
+ w = bounds.w,
+ h = bounds.h,
fill = shape.fill,
angle = info.angle,
startPos = info.gradientStart,
@@ -22,13 +22,14 @@ PIE.BorderImageRenderer = PIE.RendererBase.newRenderer( {
updateSize: function() {
if( this.isActive() ) {
var props = this.styleInfos.borderImageInfo.getProps(),
+ bounds = this.boundsInfo.getBounds(),
box = this.getBox(), //make sure pieces are created
el = this.element,
pieces = this.pieces;
PIE.Util.withImageSize( props.src, function( imgSize ) {
- var elW = el.offsetWidth,
- elH = el.offsetHeight,
+ var elW = bounds.w,
+ elH = bounds.h,
widths = props.width,
widthT = widths.t.pixels( el ),
@@ -42,9 +42,10 @@ PIE.BorderRenderer = PIE.RendererBase.newRenderer( {
drawBorder: function() {
var el = this.element,
cs = el.currentStyle,
- w = el.offsetWidth,
- h = el.offsetHeight,
props = this.styleInfos.borderInfo.getProps(),
+ bounds = this.boundsInfo.getBounds(),
+ w = bounds.w,
+ h = bounds.h,
side, shape, stroke, s,
segments, seg, i, len;
@@ -138,7 +139,7 @@ PIE.BorderRenderer = PIE.RendererBase.newRenderer( {
*/
getBorderSegments: function( mult ) {
var el = this.element,
- elW, elH,
+ bounds, elW, elH,
borderInfo = this.styleInfos.borderInfo,
segments = [],
floor, ceil, wT, wR, wB, wL,
@@ -166,8 +167,9 @@ PIE.BorderRenderer = PIE.RendererBase.newRenderer( {
}
else {
mult = mult || 1;
- elW = el.offsetWidth;
- elH = el.offsetHeight;
+ bounds = this.boundsInfo.getBounds();
+ elW = bounds.w;
+ elH = bounds.h;
wT = widths['t'].pixels( el );
wR = widths['r'].pixels( el );
View
@@ -0,0 +1,48 @@
+/**
+ * Handles calculating, caching, and detecting changes to size and position of the element.
+ * @constructor
+ * @param {Element} el the target element
+ */
+PIE.BoundsInfo = function( el ) {
+ this.element = el;
+};
+PIE.BoundsInfo.prototype = {
+
+ _lastBounds: {},
+
+ positionChanged: function() {
+ var last = this._lastBounds,
+ bounds = this.getBounds();
+ return !last || last.x !== bounds.x || last.y !== bounds.y;
+ },
+
+ sizeChanged: function() {
+ var last = this._lastBounds,
+ bounds = this.getBounds();
+ return !last || last.w !== bounds.w || last.h !== bounds.h;
+ },
+
+ getLiveBounds: function() {
+ var rect = this.element.getBoundingClientRect();
+ return {
+ x: rect.left,
+ y: rect.top,
+ w: rect.right - rect.left,
+ h: rect.bottom - rect.top
+ };
+ },
+
+ getBounds: function() {
+ return this._lockedBounds || this.getLiveBounds();
+ },
+
+ lock: function() {
+ this._lockedBounds = this.getLiveBounds();
+ },
+
+ unlock: function() {
+ this._lastBounds = this._lockedBounds;
+ this._lockedBounds = null;
+ }
+
+};
@@ -30,8 +30,9 @@ PIE.BoxShadowOutsetRenderer = PIE.RendererBase.newRenderer( {
radii = styleInfos.borderRadiusInfo.getProps(),
len = shadowInfos.length,
i = len, j,
- w = el.offsetWidth,
- h = el.offsetHeight,
+ bounds = this.boundsInfo.getBounds(),
+ w = bounds.w,
+ h = bounds.h,
clipAdjust = PIE.ieVersion === 8 ? 1 : 0, //workaround for IE8 bug where VML leaks out top/left of clip region by 1px
corners = [ 'tl', 'tr', 'br', 'bl' ], corner,
shadowInfo, shape, fill, ss, xOff, yOff, spread, blur, shrink, color, alpha, path,
View
@@ -6,6 +6,7 @@ PIE.Element = (function() {
function Element( el ) {
var lastW, lastH, lastX, lastY,
renderers,
+ boundsInfo,
styleInfos,
ancestors,
initializing,
@@ -50,6 +51,7 @@ PIE.Element = (function() {
PIE.OnScroll.unobserve( init );
// Create the style infos and renderers
+ boundsInfo = new PIE.BoundsInfo( el );
styleInfos = {
backgroundInfo: new PIE.BackgroundStyleInfo( el ),
borderInfo: new PIE.BorderStyleInfo( el ),
@@ -59,14 +61,14 @@ PIE.Element = (function() {
visibilityInfo: new PIE.VisibilityStyleInfo( el )
};
- rootRenderer = new PIE.RootRenderer( el, styleInfos );
+ rootRenderer = new PIE.RootRenderer( el, boundsInfo, styleInfos );
renderers = [
rootRenderer,
- new PIE.BoxShadowOutsetRenderer( el, styleInfos, rootRenderer ),
- new PIE.BackgroundRenderer( el, styleInfos, rootRenderer ),
- new PIE.BoxShadowInsetRenderer( el, styleInfos, rootRenderer ),
- new PIE.BorderRenderer( el, styleInfos, rootRenderer ),
- new PIE.BorderImageRenderer( el, styleInfos, rootRenderer )
+ new PIE.BoxShadowOutsetRenderer( el, boundsInfo, styleInfos, rootRenderer ),
+ new PIE.BackgroundRenderer( el, boundsInfo, styleInfos, rootRenderer ),
+ new PIE.BoxShadowInsetRenderer( el, boundsInfo, styleInfos, rootRenderer ),
+ new PIE.BorderRenderer( el, boundsInfo, styleInfos, rootRenderer ),
+ new PIE.BorderImageRenderer( el, boundsInfo, styleInfos, rootRenderer )
];
// Add property change listeners to ancestors if requested
@@ -94,33 +96,27 @@ PIE.Element = (function() {
*/
function update() {
if( initialized ) {
- /* TODO just using getBoundingClientRect may not always be accurate; it's possible that
- an element will actually move relative to its positioning parent, but its position
- relative to the viewport will stay the same. Need to come up with a better way to
- track movement. The most accurate would be the same logic used in RootRenderer.updatePos()
- but that is a more expensive operation since it does some DOM walking, and we want this
- check to be as fast as possible. */
- var rect = el.getBoundingClientRect(),
- x = rect.left,
- y = rect.top,
- w = rect.right - x,
- h = rect.bottom - y,
- i, len;
-
- if( x !== lastX || y !== lastY ) {
+ var i, len;
+
+ boundsInfo.lock();
+ if( boundsInfo.positionChanged() ) {
+ /* TODO just using getBoundingClientRect (used internally by BoundsInfo) for detecting
+ position changes may not always be accurate; it's possible that
+ an element will actually move relative to its positioning parent, but its position
+ relative to the viewport will stay the same. Need to come up with a better way to
+ track movement. The most accurate would be the same logic used in RootRenderer.updatePos()
+ but that is a more expensive operation since it does some DOM walking, and we want this
+ check to be as fast as possible. */
for( i = 0, len = renderers.length; i < len; i++ ) {
renderers[i].updatePos();
}
- lastX = x;
- lastY = y;
}
- if( w !== lastW || h !== lastH ) {
+ if( boundsInfo.sizeChanged() ) {
for( i = 0, len = renderers.length; i < len; i++ ) {
renderers[i].updateSize();
}
- lastW = w;
- lastH = h;
}
+ boundsInfo.unlock();
}
else if( !initializing ) {
init();
@@ -134,6 +130,9 @@ PIE.Element = (function() {
if( initialized ) {
var i, len,
toUpdate = [];
+
+ boundsInfo.lock();
+
for( i = 0, len = renderers.length; i < len; i++ ) {
if( renderers[i].needsUpdate() ) {
toUpdate.push( renderers[i] );
@@ -142,6 +141,8 @@ PIE.Element = (function() {
for( i = 0, len = toUpdate.length; i < len; i++ ) {
toUpdate[i].updateProps();
}
+
+ boundsInfo.unlock();
}
else if( !initializing ) {
init();
@@ -6,8 +6,9 @@ PIE.RendererBase = {
* @param proto
*/
newRenderer: function( proto ) {
- function Renderer( el, styleInfos, parent ) {
+ function Renderer( el, boundsInfo, styleInfos, parent ) {
this.element = el;
+ this.boundsInfo = boundsInfo;
this.styleInfos = styleInfos;
this.parent = parent;
}
@@ -147,8 +148,9 @@ PIE.RendererBase = {
*/
getRadiiPixels: function( radii ) {
var el = this.element,
- w = el.offsetWidth,
- h = el.offsetHeight,
+ bounds = this.boundsInfo.getBounds(),
+ w = bounds.w,
+ h = bounds.h,
tlX, tlY, trX, trY, brX, brY, blX, blY, f;
tlX = radii.x['tl'].pixels( el, w );
@@ -208,9 +210,9 @@ PIE.RendererBase = {
mult = mult || 1;
var r, str,
- el = this.element,
- w = el.offsetWidth * mult,
- h = el.offsetHeight * mult,
+ bounds = this.boundsInfo.getBounds(),
+ w = bounds.w * mult,
+ h = bounds.h * mult,
radInfo = this.styleInfos.borderRadiusInfo,
floor = Math.floor, ceil = Math.ceil,
shrinkT = shrink ? shrink.t * mult : 0,
@@ -54,5 +54,13 @@ PIE.StyleInfoBase = {
*/
changed: function() {
return this._lastCss !== this.getCss();
+ },
+
+ lock: function() {
+ this._locked = 1;
+ },
+
+ unlock: function() {
+ this._locked = 0;
}
};

0 comments on commit 04ed5d5

Please sign in to comment.