Skip to content
This repository
Browse code

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...
commit 04ed5d5bb3138975e8fa21e72e9041578ec112c3 1 parent 6b374bb
Jason Johnston authored
1  build.xml
@@ -24,6 +24,7 @@
24 24 <fileset file="${src_dir}/Angle.js" />
25 25 <fileset file="${src_dir}/Color.js" />
26 26 <fileset file="${src_dir}/Tokenizer.js" />
  27 + <fileset file="${src_dir}/BoundsInfo.js" />
27 28 <fileset file="${src_dir}/StyleInfoBase.js" />
28 29 <fileset file="${src_dir}/BackgroundStyleInfo.js" />
29 30 <fileset file="${src_dir}/BorderStyleInfo.js" />
28 sources/BackgroundRenderer.js
@@ -17,8 +17,8 @@ PIE.BackgroundRenderer = PIE.RendererBase.newRenderer( {
17 17
18 18 isActive: function() {
19 19 var si = this.styleInfos,
20   - el = this.element;
21   - return el.offsetWidth && el.offsetHeight && (
  20 + bounds = this.boundsInfo.getBounds();
  21 + return bounds.w && bounds.h && (
22 22 si.borderImageInfo.isActive() ||
23 23 si.borderRadiusInfo.isActive() ||
24 24 si.backgroundInfo.isActive() ||
@@ -51,6 +51,7 @@ PIE.BackgroundRenderer = PIE.RendererBase.newRenderer( {
51 51 */
52 52 drawBgColor: function() {
53 53 var props = this.styleInfos.backgroundInfo.getProps(),
  54 + bounds = this.boundsInfo.getBounds(),
54 55 el = this.element,
55 56 color = props && props.color,
56 57 shape, w, h, s, alpha;
@@ -59,8 +60,8 @@ PIE.BackgroundRenderer = PIE.RendererBase.newRenderer( {
59 60 this.hideBackground();
60 61
61 62 shape = this.getShape( 'bgColor', 'fill', this.getBox(), 1 );
62   - w = el.offsetWidth;
63   - h = el.offsetHeight;
  63 + w = bounds.w;
  64 + h = bounds.h;
64 65 shape.stroked = false;
65 66 shape.coordsize = w * 2 + ',' + h * 2;
66 67 shape.coordorigin = '1,1';
@@ -84,15 +85,15 @@ PIE.BackgroundRenderer = PIE.RendererBase.newRenderer( {
84 85 */
85 86 drawBgImages: function() {
86 87 var props = this.styleInfos.backgroundInfo.getProps(),
  88 + bounds = this.boundsInfo.getBounds(),
87 89 images = props && props.images,
88   - img, el, shape, w, h, s, i;
  90 + img, shape, w, h, s, i;
89 91
90 92 if( images ) {
91 93 this.hideBackground();
92 94
93   - el = this.element;
94   - w = el.offsetWidth,
95   - h = el.offsetHeight,
  95 + w = bounds.w;
  96 + h = bounds.h;
96 97
97 98 i = images.length;
98 99 while( i-- ) {
@@ -134,9 +135,9 @@ PIE.BackgroundRenderer = PIE.RendererBase.newRenderer( {
134 135 PIE.Util.withImageSize( shape.fill.src, function( size ) {
135 136 var fill = shape.fill,
136 137 el = this.element,
137   - elW = el.offsetWidth,
138   - elH = el.offsetHeight,
139   - cs = el.currentStyle,
  138 + bounds = this.boundsInfo.getBounds(),
  139 + elW = bounds.w,
  140 + elH = bounds.h,
140 141 si = this.styleInfos,
141 142 border = si.borderInfo.getProps(),
142 143 bw = border && border.widths,
@@ -182,8 +183,9 @@ PIE.BackgroundRenderer = PIE.RendererBase.newRenderer( {
182 183 */
183 184 addLinearGradient: function( shape, info ) {
184 185 var el = this.element,
185   - w = el.offsetWidth,
186   - h = el.offsetHeight,
  186 + bounds = this.boundsInfo.getBounds(),
  187 + w = bounds.w,
  188 + h = bounds.h,
187 189 fill = shape.fill,
188 190 angle = info.angle,
189 191 startPos = info.gradientStart,
5 sources/BorderImageRenderer.js
@@ -22,13 +22,14 @@ PIE.BorderImageRenderer = PIE.RendererBase.newRenderer( {
22 22 updateSize: function() {
23 23 if( this.isActive() ) {
24 24 var props = this.styleInfos.borderImageInfo.getProps(),
  25 + bounds = this.boundsInfo.getBounds(),
25 26 box = this.getBox(), //make sure pieces are created
26 27 el = this.element,
27 28 pieces = this.pieces;
28 29
29 30 PIE.Util.withImageSize( props.src, function( imgSize ) {
30   - var elW = el.offsetWidth,
31   - elH = el.offsetHeight,
  31 + var elW = bounds.w,
  32 + elH = bounds.h,
32 33
33 34 widths = props.width,
34 35 widthT = widths.t.pixels( el ),
12 sources/BorderRenderer.js
@@ -42,9 +42,10 @@ PIE.BorderRenderer = PIE.RendererBase.newRenderer( {
42 42 drawBorder: function() {
43 43 var el = this.element,
44 44 cs = el.currentStyle,
45   - w = el.offsetWidth,
46   - h = el.offsetHeight,
47 45 props = this.styleInfos.borderInfo.getProps(),
  46 + bounds = this.boundsInfo.getBounds(),
  47 + w = bounds.w,
  48 + h = bounds.h,
48 49 side, shape, stroke, s,
49 50 segments, seg, i, len;
50 51
@@ -138,7 +139,7 @@ PIE.BorderRenderer = PIE.RendererBase.newRenderer( {
138 139 */
139 140 getBorderSegments: function( mult ) {
140 141 var el = this.element,
141   - elW, elH,
  142 + bounds, elW, elH,
142 143 borderInfo = this.styleInfos.borderInfo,
143 144 segments = [],
144 145 floor, ceil, wT, wR, wB, wL,
@@ -166,8 +167,9 @@ PIE.BorderRenderer = PIE.RendererBase.newRenderer( {
166 167 }
167 168 else {
168 169 mult = mult || 1;
169   - elW = el.offsetWidth;
170   - elH = el.offsetHeight;
  170 + bounds = this.boundsInfo.getBounds();
  171 + elW = bounds.w;
  172 + elH = bounds.h;
171 173
172 174 wT = widths['t'].pixels( el );
173 175 wR = widths['r'].pixels( el );
48 sources/BoundsInfo.js
... ... @@ -0,0 +1,48 @@
  1 +/**
  2 + * Handles calculating, caching, and detecting changes to size and position of the element.
  3 + * @constructor
  4 + * @param {Element} el the target element
  5 + */
  6 +PIE.BoundsInfo = function( el ) {
  7 + this.element = el;
  8 +};
  9 +PIE.BoundsInfo.prototype = {
  10 +
  11 + _lastBounds: {},
  12 +
  13 + positionChanged: function() {
  14 + var last = this._lastBounds,
  15 + bounds = this.getBounds();
  16 + return !last || last.x !== bounds.x || last.y !== bounds.y;
  17 + },
  18 +
  19 + sizeChanged: function() {
  20 + var last = this._lastBounds,
  21 + bounds = this.getBounds();
  22 + return !last || last.w !== bounds.w || last.h !== bounds.h;
  23 + },
  24 +
  25 + getLiveBounds: function() {
  26 + var rect = this.element.getBoundingClientRect();
  27 + return {
  28 + x: rect.left,
  29 + y: rect.top,
  30 + w: rect.right - rect.left,
  31 + h: rect.bottom - rect.top
  32 + };
  33 + },
  34 +
  35 + getBounds: function() {
  36 + return this._lockedBounds || this.getLiveBounds();
  37 + },
  38 +
  39 + lock: function() {
  40 + this._lockedBounds = this.getLiveBounds();
  41 + },
  42 +
  43 + unlock: function() {
  44 + this._lastBounds = this._lockedBounds;
  45 + this._lockedBounds = null;
  46 + }
  47 +
  48 +};
5 sources/BoxShadowOutsetRenderer.js
@@ -30,8 +30,9 @@ PIE.BoxShadowOutsetRenderer = PIE.RendererBase.newRenderer( {
30 30 radii = styleInfos.borderRadiusInfo.getProps(),
31 31 len = shadowInfos.length,
32 32 i = len, j,
33   - w = el.offsetWidth,
34   - h = el.offsetHeight,
  33 + bounds = this.boundsInfo.getBounds(),
  34 + w = bounds.w,
  35 + h = bounds.h,
35 36 clipAdjust = PIE.ieVersion === 8 ? 1 : 0, //workaround for IE8 bug where VML leaks out top/left of clip region by 1px
36 37 corners = [ 'tl', 'tr', 'br', 'bl' ], corner,
37 38 shadowInfo, shape, fill, ss, xOff, yOff, spread, blur, shrink, color, alpha, path,
51 sources/Element.js
@@ -6,6 +6,7 @@ PIE.Element = (function() {
6 6 function Element( el ) {
7 7 var lastW, lastH, lastX, lastY,
8 8 renderers,
  9 + boundsInfo,
9 10 styleInfos,
10 11 ancestors,
11 12 initializing,
@@ -50,6 +51,7 @@ PIE.Element = (function() {
50 51 PIE.OnScroll.unobserve( init );
51 52
52 53 // Create the style infos and renderers
  54 + boundsInfo = new PIE.BoundsInfo( el );
53 55 styleInfos = {
54 56 backgroundInfo: new PIE.BackgroundStyleInfo( el ),
55 57 borderInfo: new PIE.BorderStyleInfo( el ),
@@ -59,14 +61,14 @@ PIE.Element = (function() {
59 61 visibilityInfo: new PIE.VisibilityStyleInfo( el )
60 62 };
61 63
62   - rootRenderer = new PIE.RootRenderer( el, styleInfos );
  64 + rootRenderer = new PIE.RootRenderer( el, boundsInfo, styleInfos );
63 65 renderers = [
64 66 rootRenderer,
65   - new PIE.BoxShadowOutsetRenderer( el, styleInfos, rootRenderer ),
66   - new PIE.BackgroundRenderer( el, styleInfos, rootRenderer ),
67   - new PIE.BoxShadowInsetRenderer( el, styleInfos, rootRenderer ),
68   - new PIE.BorderRenderer( el, styleInfos, rootRenderer ),
69   - new PIE.BorderImageRenderer( el, styleInfos, rootRenderer )
  67 + new PIE.BoxShadowOutsetRenderer( el, boundsInfo, styleInfos, rootRenderer ),
  68 + new PIE.BackgroundRenderer( el, boundsInfo, styleInfos, rootRenderer ),
  69 + new PIE.BoxShadowInsetRenderer( el, boundsInfo, styleInfos, rootRenderer ),
  70 + new PIE.BorderRenderer( el, boundsInfo, styleInfos, rootRenderer ),
  71 + new PIE.BorderImageRenderer( el, boundsInfo, styleInfos, rootRenderer )
70 72 ];
71 73
72 74 // Add property change listeners to ancestors if requested
@@ -94,33 +96,27 @@ PIE.Element = (function() {
94 96 */
95 97 function update() {
96 98 if( initialized ) {
97   - /* TODO just using getBoundingClientRect may not always be accurate; it's possible that
98   - an element will actually move relative to its positioning parent, but its position
99   - relative to the viewport will stay the same. Need to come up with a better way to
100   - track movement. The most accurate would be the same logic used in RootRenderer.updatePos()
101   - but that is a more expensive operation since it does some DOM walking, and we want this
102   - check to be as fast as possible. */
103   - var rect = el.getBoundingClientRect(),
104   - x = rect.left,
105   - y = rect.top,
106   - w = rect.right - x,
107   - h = rect.bottom - y,
108   - i, len;
109   -
110   - if( x !== lastX || y !== lastY ) {
  99 + var i, len;
  100 +
  101 + boundsInfo.lock();
  102 + if( boundsInfo.positionChanged() ) {
  103 + /* TODO just using getBoundingClientRect (used internally by BoundsInfo) for detecting
  104 + position changes may not always be accurate; it's possible that
  105 + an element will actually move relative to its positioning parent, but its position
  106 + relative to the viewport will stay the same. Need to come up with a better way to
  107 + track movement. The most accurate would be the same logic used in RootRenderer.updatePos()
  108 + but that is a more expensive operation since it does some DOM walking, and we want this
  109 + check to be as fast as possible. */
111 110 for( i = 0, len = renderers.length; i < len; i++ ) {
112 111 renderers[i].updatePos();
113 112 }
114   - lastX = x;
115   - lastY = y;
116 113 }
117   - if( w !== lastW || h !== lastH ) {
  114 + if( boundsInfo.sizeChanged() ) {
118 115 for( i = 0, len = renderers.length; i < len; i++ ) {
119 116 renderers[i].updateSize();
120 117 }
121   - lastW = w;
122   - lastH = h;
123 118 }
  119 + boundsInfo.unlock();
124 120 }
125 121 else if( !initializing ) {
126 122 init();
@@ -134,6 +130,9 @@ PIE.Element = (function() {
134 130 if( initialized ) {
135 131 var i, len,
136 132 toUpdate = [];
  133 +
  134 + boundsInfo.lock();
  135 +
137 136 for( i = 0, len = renderers.length; i < len; i++ ) {
138 137 if( renderers[i].needsUpdate() ) {
139 138 toUpdate.push( renderers[i] );
@@ -142,6 +141,8 @@ PIE.Element = (function() {
142 141 for( i = 0, len = toUpdate.length; i < len; i++ ) {
143 142 toUpdate[i].updateProps();
144 143 }
  144 +
  145 + boundsInfo.unlock();
145 146 }
146 147 else if( !initializing ) {
147 148 init();
14 sources/RendererBase.js
@@ -6,8 +6,9 @@ PIE.RendererBase = {
6 6 * @param proto
7 7 */
8 8 newRenderer: function( proto ) {
9   - function Renderer( el, styleInfos, parent ) {
  9 + function Renderer( el, boundsInfo, styleInfos, parent ) {
10 10 this.element = el;
  11 + this.boundsInfo = boundsInfo;
11 12 this.styleInfos = styleInfos;
12 13 this.parent = parent;
13 14 }
@@ -147,8 +148,9 @@ PIE.RendererBase = {
147 148 */
148 149 getRadiiPixels: function( radii ) {
149 150 var el = this.element,
150   - w = el.offsetWidth,
151   - h = el.offsetHeight,
  151 + bounds = this.boundsInfo.getBounds(),
  152 + w = bounds.w,
  153 + h = bounds.h,
152 154 tlX, tlY, trX, trY, brX, brY, blX, blY, f;
153 155
154 156 tlX = radii.x['tl'].pixels( el, w );
@@ -208,9 +210,9 @@ PIE.RendererBase = {
208 210 mult = mult || 1;
209 211
210 212 var r, str,
211   - el = this.element,
212   - w = el.offsetWidth * mult,
213   - h = el.offsetHeight * mult,
  213 + bounds = this.boundsInfo.getBounds(),
  214 + w = bounds.w * mult,
  215 + h = bounds.h * mult,
214 216 radInfo = this.styleInfos.borderRadiusInfo,
215 217 floor = Math.floor, ceil = Math.ceil,
216 218 shrinkT = shrink ? shrink.t * mult : 0,
8 sources/StyleInfoBase.js
@@ -54,5 +54,13 @@ PIE.StyleInfoBase = {
54 54 */
55 55 changed: function() {
56 56 return this._lastCss !== this.getCss();
  57 + },
  58 +
  59 + lock: function() {
  60 + this._locked = 1;
  61 + },
  62 +
  63 + unlock: function() {
  64 + this._locked = 0;
57 65 }
58 66 };

0 comments on commit 04ed5d5

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