Skip to content

Commit

Permalink
Move all accesses of element dimensions into a new BoundsInfo object,…
Browse files Browse the repository at this point in the history
… 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
Jason Johnston committed Sep 1, 2010
1 parent 6b374bb commit 04ed5d5
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 53 deletions.
1 change: 1 addition & 0 deletions build.xml
Expand Up @@ -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" />
Expand Down
28 changes: 15 additions & 13 deletions sources/BackgroundRenderer.js
Expand Up @@ -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() ||
Expand Down Expand Up @@ -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;
Expand All @@ -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';
Expand All @@ -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-- ) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions sources/BorderImageRenderer.js
Expand Up @@ -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 ),
Expand Down
12 changes: 7 additions & 5 deletions sources/BorderRenderer.js
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 );
Expand Down
48 changes: 48 additions & 0 deletions sources/BoundsInfo.js
@@ -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;
}

};
5 changes: 3 additions & 2 deletions sources/BoxShadowOutsetRenderer.js
Expand Up @@ -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,
Expand Down
51 changes: 26 additions & 25 deletions sources/Element.js
Expand Up @@ -6,6 +6,7 @@ PIE.Element = (function() {
function Element( el ) {
var lastW, lastH, lastX, lastY,
renderers,
boundsInfo,
styleInfos,
ancestors,
initializing,
Expand Down Expand Up @@ -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 ),
Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand All @@ -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] );
Expand All @@ -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();
Expand Down
14 changes: 8 additions & 6 deletions sources/RendererBase.js
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions sources/StyleInfoBase.js
Expand Up @@ -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.