diff --git a/sources/BoundsInfo.js b/sources/BoundsInfo.js index 2433659..8ef9d30 100644 --- a/sources/BoundsInfo.js +++ b/sources/BoundsInfo.js @@ -10,6 +10,17 @@ PIE.BoundsInfo.prototype = { _locked: 0, + /** + * Determines if the element's position has changed since the last update. + * TODO this does a simple getBoundingClientRect comparison for detecting the + * changes in position, which 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 performs DOM walking, and we want this + * check to be as fast as possible. Perhaps introduce a -pie-* flag to trigger the slower + * but more accurate method? + */ positionChanged: function() { var last = this._lastBounds, bounds; diff --git a/sources/Element.js b/sources/Element.js index ba7ab8b..61da965 100644 --- a/sources/Element.js +++ b/sources/Element.js @@ -53,7 +53,7 @@ PIE.Element = (function() { function Element( el ) { var me = this, - renderers, + childRenderers, rootRenderer, boundsInfo = new PIE.BoundsInfo( el ), styleInfos, @@ -79,8 +79,7 @@ PIE.Element = (function() { cs = el.currentStyle, lazy = cs.getAttribute( lazyInitCssProp ) === 'true', trackActive = cs.getAttribute( trackActiveCssProp ) !== 'false', - trackHover = cs.getAttribute( trackHoverCssProp ) !== 'false', - childRenderers; + trackHover = cs.getAttribute( trackHoverCssProp ) !== 'false'; // Polling for size/position changes: default to on in IE8, off otherwise, overridable by -pie-poll poll = cs.getAttribute( pollCssProp ); @@ -160,7 +159,6 @@ PIE.Element = (function() { } rootRenderer.childRenderers = childRenderers; // circular reference, can't pass in constructor; TODO is there a cleaner way? } - renderers = [ rootRenderer ].concat( childRenderers ); // Add property change listeners to ancestors if requested initAncestorEventListeners(); @@ -229,28 +227,18 @@ PIE.Element = (function() { if( initialized ) { lockAll(); - var i, len = renderers.length, + var i = 0, len = childRenderers.length, sizeChanged = boundsInfo.sizeChanged(); - - for( i = 0; i < len; i++ ) { - renderers[i].prepareUpdate(); + for( ; i < len; i++ ) { + childRenderers[i].prepareUpdate(); } for( i = 0; i < len; i++ ) { - if( force || sizeChanged || ( isPropChange && renderers[i].needsUpdate() ) ) { - renderers[i].updateRendering(); + if( force || sizeChanged || ( isPropChange && childRenderers[i].needsUpdate() ) ) { + childRenderers[i].updateRendering(); } } - rootRenderer.finishUpdate(); - - if( force || ( ( !isPropChange || rootRenderer.isPositioned ) && 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. */ - rootRenderer.updatePos(); + if( force || sizeChanged || isPropChange || boundsInfo.positionChanged() ) { + rootRenderer.updateRendering(); } unlockAll(); @@ -265,14 +253,12 @@ PIE.Element = (function() { * Handle property changes to trigger update when appropriate. */ function propChanged() { - var e = event; - // Some elements like fire onpropertychange events for old-school background properties // ('background', 'bgColor') when runtimeStyle background properties are changed, which // results in an infinite loop; therefore we filter out those property names. Also, 'display' // is ignored because size calculations don't work correctly immediately when its onpropertychange // event fires, and because it will trigger an onresize event anyway. - if( !( e && e.propertyName in ignorePropertyNames ) ) { + if( initialized && !( event && event.propertyName in ignorePropertyNames ) ) { update( 1 ); } } @@ -341,7 +327,7 @@ PIE.Element = (function() { */ function ancestorPropChanged() { var name = event.propertyName; - if( name === 'className' || name === 'id' ) { + if( name === 'className' || name === 'id' || name.indexOf( 'style.' ) === 0 ) { propChanged(); } } @@ -399,12 +385,13 @@ PIE.Element = (function() { destroyed = 1; // destroy any active renderers - if( renderers ) { - for( i = 0, len = renderers.length; i < len; i++ ) { - renderers[i].finalized = 1; - renderers[i].destroy(); + if( childRenderers ) { + for( i = 0, len = childRenderers.length; i < len; i++ ) { + childRenderers[i].finalized = 1; + childRenderers[i].destroy(); } } + rootRenderer.destroy(); // Remove from list of polled elements in IE8 if( poll ) { @@ -414,7 +401,7 @@ PIE.Element = (function() { PIE.OnResize.unobserve( update ); // Kill references - renderers = boundsInfo = styleInfos = styleInfosArr = el = null; + childRenderers = rootRenderer = boundsInfo = styleInfos = styleInfosArr = el = null; me.el = me = 0; } } diff --git a/sources/IE9BorderImageRenderer.js b/sources/IE9BorderImageRenderer.js index 103b152..538fbe9 100644 --- a/sources/IE9BorderImageRenderer.js +++ b/sources/IE9BorderImageRenderer.js @@ -137,7 +137,7 @@ PIE.IE9BorderImageRenderer = PIE.RendererBase.newRenderer( { // will have been created asynchronously after the main element's update has finished; we'll // therefore need to force the root renderer to sync to the final background once finished. if( isAsync ) { - me.parent.finishUpdate(); + me.parent.updateRendering(); } }, me ); diff --git a/sources/IE9RootRenderer.js b/sources/IE9RootRenderer.js index dbe6b8b..6e0d5ac 100644 --- a/sources/IE9RootRenderer.js +++ b/sources/IE9RootRenderer.js @@ -5,10 +5,6 @@ */ PIE.IE9RootRenderer = PIE.RendererBase.newRenderer( { - updatePos: PIE.emptyFn, - updateRendering: PIE.emptyFn, - updateVisibility: PIE.emptyFn, - outerCommasRE: /^,+|,+$/g, innerCommasRE: /,+/g, @@ -19,7 +15,7 @@ PIE.IE9RootRenderer = PIE.RendererBase.newRenderer( { bgLayers[zIndex] = bg || undef; }, - finishUpdate: function() { + updateRendering: function() { var me = this, bgLayers = me._bgLayers, bg; diff --git a/sources/RootRenderer.js b/sources/RootRenderer.js index 2c1889f..4b1c4b9 100644 --- a/sources/RootRenderer.js +++ b/sources/RootRenderer.js @@ -6,12 +6,6 @@ */ PIE.RootRenderer = PIE.RendererBase.newRenderer( { - /** - * Flag indicating the element has already been positioned at least once. - * @type {boolean} - */ - isPositioned: false, - isActive: function() { var children = this.childRenderers; for( var i in children ) { @@ -22,72 +16,58 @@ PIE.RootRenderer = PIE.RendererBase.newRenderer( { return false; }, - needsUpdate: function() { - return this.styleInfos.visibilityInfo.changed(); - }, - - /** - * Tell the renderer to update based on modified element position - */ - updatePos: function() { - if( this.isActive() && this.getBoxEl() ) { - var el = this.getPositioningElement(), - par = el, - docEl, - parRect, - tgtCS = el.currentStyle, - tgtPos = tgtCS.position, - boxPos, - s = this.getBoxEl().style, cs, - x = 0, y = 0, - elBounds = this.boundsInfo.getBounds(), - logicalZoomRatio = elBounds.logicalZoomRatio; - - if( tgtPos === 'fixed' && PIE.ieVersion > 6 ) { - x = elBounds.x * logicalZoomRatio; - y = elBounds.y * logicalZoomRatio; - boxPos = tgtPos; + getBoxCssText: function() { + var el = this.getPositioningElement(), + par = el, + docEl, + parRect, + tgtCS = el.currentStyle, + tgtPos = tgtCS.position, + boxPos, + cs, + x = 0, y = 0, + elBounds = this.boundsInfo.getBounds(), + vis = this.styleInfos.visibilityInfo.getProps(), + logicalZoomRatio = elBounds.logicalZoomRatio; + + if( tgtPos === 'fixed' && PIE.ieVersion > 6 ) { + x = elBounds.x * logicalZoomRatio; + y = elBounds.y * logicalZoomRatio; + boxPos = tgtPos; + } else { + // Get the element's offsets from its nearest positioned ancestor. Uses + // getBoundingClientRect for accuracy and speed. + do { + par = par.offsetParent; + } while( par && ( par.currentStyle.position === 'static' ) ); + if( par ) { + parRect = par.getBoundingClientRect(); + cs = par.currentStyle; + x = ( elBounds.x - parRect.left ) * logicalZoomRatio - ( parseFloat(cs.borderLeftWidth) || 0 ); + y = ( elBounds.y - parRect.top ) * logicalZoomRatio - ( parseFloat(cs.borderTopWidth) || 0 ); } else { - // Get the element's offsets from its nearest positioned ancestor. Uses - // getBoundingClientRect for accuracy and speed. - do { - par = par.offsetParent; - } while( par && ( par.currentStyle.position === 'static' ) ); - if( par ) { - parRect = par.getBoundingClientRect(); - cs = par.currentStyle; - x = ( elBounds.x - parRect.left ) * logicalZoomRatio - ( parseFloat(cs.borderLeftWidth) || 0 ); - y = ( elBounds.y - parRect.top ) * logicalZoomRatio - ( parseFloat(cs.borderTopWidth) || 0 ); - } else { - docEl = doc.documentElement; - x = ( elBounds.x + docEl.scrollLeft - docEl.clientLeft ) * logicalZoomRatio; - y = ( elBounds.y + docEl.scrollTop - docEl.clientTop ) * logicalZoomRatio; - } - boxPos = 'absolute'; + docEl = doc.documentElement; + x = ( elBounds.x + docEl.scrollLeft - docEl.clientLeft ) * logicalZoomRatio; + y = ( elBounds.y + docEl.scrollTop - docEl.clientTop ) * logicalZoomRatio; } - - s.position = boxPos; - s.left = x; - s.top = y; - s.zIndex = tgtPos === 'static' ? -1 : tgtCS.zIndex; - this.isPositioned = true; + boxPos = 'absolute'; } - }, - updateVisibility: function() { - var vis = this.styleInfos.visibilityInfo, - box = this._box; - if ( box && vis.changed() ) { - vis = vis.getProps(); - box.style.display = ( vis.visible && vis.displayed ) ? '' : 'none'; - } + return 'direction:ltr;' + + 'position:absolute;' + + 'behavior:none !important;' + + 'position:' + boxPos + ';' + + 'left:' + x + 'px;' + + 'top:' + y + 'px;' + + 'z-index:' + ( tgtPos === 'static' ? -1 : tgtCS.zIndex ) + ';' + + 'display:' + ( vis.visible && vis.displayed ? 'block' : 'none' ); }, - updateRendering: function() { - if( this.isActive() ) { - this.updateVisibility(); - } else { - this.destroy(); + updateBoxStyles: function() { + var me = this, + boxEl = me.getBoxEl(); + if( boxEl && ( me.boundsInfo.positionChanged() || me.styleInfos.visibilityInfo.changed() ) ) { + boxEl.style.cssText = me.getBoxCssText(); } }, @@ -111,48 +91,59 @@ PIE.RootRenderer = PIE.RendererBase.newRenderer( { /** * Render any child rendrerer shapes which have not already been rendered into the DOM. */ - finishUpdate: function() { + updateRendering: function() { var me = this, queue = me._shapeRenderQueue, renderedShapes, markup, i, len, j, - ref, pos; - - if( queue ) { - // We've already rendered something once, so do incremental insertion of new shapes - renderedShapes = me._renderedShapes; - if( renderedShapes ) { - for( i = 0, len = queue.length; i < len; i++ ) { - for( j = renderedShapes.length; j--; ) { - if( renderedShapes[ j ].ordinalGroup < queue[ i ].ordinalGroup ) { - break; + ref, pos, vis; + + if (me.isActive()) { + if( queue ) { + // We've already rendered something once, so do incremental insertion of new shapes + renderedShapes = me._renderedShapes; + if( renderedShapes ) { + for( i = 0, len = queue.length; i < len; i++ ) { + for( j = renderedShapes.length; j--; ) { + if( renderedShapes[ j ].ordinalGroup < queue[ i ].ordinalGroup ) { + break; + } } - } - if ( j < 0 ) { - ref = me.getBoxEl(); - pos = 'afterBegin'; - } else { - ref = renderedShapes[ j ].getShape(); - pos = 'afterEnd'; + if ( j < 0 ) { + ref = me.getBoxEl(); + pos = 'afterBegin'; + } else { + ref = renderedShapes[ j ].getShape(); + pos = 'afterEnd'; + } + ref.insertAdjacentHTML( pos, queue[ i ].getMarkup() ); + renderedShapes.splice( j < 0 ? 0 : j, 0, queue[ i ] ); } - ref.insertAdjacentHTML( pos, queue[ i ].getMarkup() ); - renderedShapes.splice( j < 0 ? 0 : j, 0, queue[ i ] ); - } - } - // This is the first render, so build up a single markup string and insert it all at once - else { - queue.sort( me.shapeSorter ); - markup = [ '' ]; - for( i = 0, len = queue.length; i < len; i++ ) { - markup.push( queue[ i ].getMarkup() ); + me._shapeRenderQueue = 0; + me.updateBoxStyles(); } - markup.push( '' ); + // This is the first render, so build up a single markup string and insert it all at once + else { + vis = me.styleInfos.visibilityInfo.getProps(); + if( vis.visible && vis.displayed ) { + queue.sort( me.shapeSorter ); + markup = [ '' ]; + for( i = 0, len = queue.length; i < len; i++ ) { + markup.push( queue[ i ].getMarkup() ); + } + markup.push( '' ); - me.getPositioningElement().insertAdjacentHTML( 'beforeBegin', markup.join( '' ) ); + me.getPositioningElement().insertAdjacentHTML( 'beforeBegin', markup.join( '' ) ); - me._renderedShapes = queue; + me._renderedShapes = queue; + me._shapeRenderQueue = 0; + } + } + } else { + me.updateBoxStyles(); } - me._shapeRenderQueue = 0; + } else { + me.destroy(); } }, diff --git a/sources/VisibilityStyleInfo.js b/sources/VisibilityStyleInfo.js index 182923d..fb9bf69 100644 --- a/sources/VisibilityStyleInfo.js +++ b/sources/VisibilityStyleInfo.js @@ -6,25 +6,23 @@ PIE.VisibilityStyleInfo = PIE.StyleInfoBase.newStyleInfo( { getCss: PIE.StyleInfoBase.cacheWhenLocked( function() { - var cs = this.targetElement.currentStyle; - return cs.visibility + '|' + cs.display; - } ), - - parseCss: function() { var el = this.targetElement, rs = el.runtimeStyle, cs = el.currentStyle, rsVis = rs.visibility, - csVis; - + ret; rs.visibility = ''; - csVis = cs.visibility; + ret = cs.visibility + '|' + cs.display; rs.visibility = rsVis; + return ret; + } ), + parseCss: function() { + var info = this.getCss().split('|'); return { - visible: csVis !== 'hidden', - displayed: cs.display !== 'none' - } + visible: info[0] !== 'hidden', + displayed: info[1] !== 'none' + }; }, /** diff --git a/tests/hiding-tests.html b/tests/hiding-tests.html index 1c51070..13dc02b 100644 --- a/tests/hiding-tests.html +++ b/tests/hiding-tests.html @@ -116,7 +116,7 @@

Toggle parent visibility, hidden initially

@@ -126,7 +126,7 @@

Toggle parent visibility, shown initially

-
Hello PIE
+
Hello PIE