@ -5,6 +5,47 @@ function setNextFrame(obj, prop, val) {
nextFrame ( function ( ) { obj [ prop ] = val ; } ) ;
}
function getTextNodeRect ( textNode ) {
var rect ;
if ( document . createRange ) {
var range = document . createRange ( ) ;
range . selectNodeContents ( textNode ) ;
if ( range . getBoundingClientRect ) {
rect = range . getBoundingClientRect ( ) ;
}
}
return rect ;
}
function calcTransformOrigin ( isTextNode , textRect , boundingRect ) {
if ( isTextNode ) {
if ( textRect ) {
//calculate pixels to center of text from left edge of bounding box
var relativeCenterX = textRect . left + textRect . width / 2 - boundingRect . left ;
var relativeCenterY = textRect . top + textRect . height / 2 - boundingRect . top ;
return relativeCenterX + 'px ' + relativeCenterY + 'px' ;
}
}
return '0 0' ; //top left
}
function getTextDx ( oldTextRect , newTextRect ) {
if ( oldTextRect && newTextRect ) {
return ( ( oldTextRect . left + oldTextRect . width / 2 ) - ( newTextRect . left + newTextRect . width / 2 ) ) ;
}
return 0 ;
}
function getTextDy ( oldTextRect , newTextRect ) {
if ( oldTextRect && newTextRect ) {
return ( ( oldTextRect . top + oldTextRect . height / 2 ) - ( newTextRect . top + newTextRect . height / 2 ) ) ;
}
return 0 ;
}
function isTextElement ( elm ) {
return elm . childNodes . length === 1 && elm . childNodes [ 0 ] . nodeType === 3 ;
}
var removed , created ;
function pre ( oldVnode , vnode ) {
@ -23,12 +64,15 @@ function create(oldVnode, vnode) {
function destroy ( vnode ) {
var hero = vnode . data . hero ;
if ( hero && hero . id ) {
vnode . boundingRect = vnode . elm . getBoundingClientRect ( ) //save the bounding rectangle to a new property on the vnode
var computedStyle = window . getComputedStyle ( vnode . elm , null ) //save current styles (includes inherited properties) - read only
var elm = vnode . elm ;
vnode . isTextNode = isTextElement ( elm ) ; //is this a text node?
vnode . boundingRect = elm . getBoundingClientRect ( ) ; //save the bounding rectangle to a new property on the vnode
vnode . textRect = vnode . isTextNode ? getTextNodeRect ( elm . childNodes [ 0 ] ) : null ; //save bounding rect of inner text node
var computedStyle = window . getComputedStyle ( elm , null ) ; //get current styles (includes inherited properties)
vnode . savedStyle = {
textAlign : computedStyle . textAlign ,
//todo: more properties?
}
color : computedStyle . color ,
} ;
removed [ hero . id ] = vnode ;
}
}
@ -36,31 +80,41 @@ function destroy(vnode) {
function post ( ) {
var i , id , newElm , oldVnode , oldElm , hRatio , wRatio ,
oldRect , newRect , dx , dy , origTransform , origTransition ,
newStyle , oldStyle , newComputedStyle , isTextNode ;
newStyle , oldStyle , newComputedStyle , isTextNode ,
newTextRect , oldTextRect ;
for ( i = 0 ; i < created . length ; i += 2 ) {
id = created [ i ] ;
newElm = created [ i + 1 ] . elm ;
oldVnode = removed [ id ] ;
if ( oldVnode ) {
isTextNode = oldVnode . isTextNode && isTextElement ( newElm ) ; //Are old & new both text?
newStyle = newElm . style ;
newComputedStyle = window . getComputedStyle ( newElm , null ) //get full computed style for new element
newComputedStyle = window . getComputedStyle ( newElm , null ) ; //get full computed style for new element
oldElm = oldVnode . elm ;
oldStyle = oldElm . style ;
//Overall element bounding boxes
newRect = newElm . getBoundingClientRect ( ) ;
oldRect = oldVnode . boundingRect ; //Use previously saved bounding rect
dx = oldRect . left - newRect . left ;
dy = oldRect . top - newRect . top ;
// Determine if these are text elements. if so, scale based on hRatio only.
isTextNode = oldElm . childNodes . length === 1 && oldElm . childNodes [ 0 ] . nodeType === 3
oldRect = oldVnode . boundingRect ; //previously saved bounding rect
//Text node bounding boxes & distances
if ( isTextNode ) {
newTextRect = getTextNodeRect ( newElm . childNodes [ 0 ] ) ;
oldTextRect = oldVnode . textRect ;
dx = getTextDx ( oldTextRect , newTextRect ) ;
dy = getTextDy ( oldTextRect , newTextRect ) ;
} else {
//Calculate distances between old & new positions
dx = oldRect . left - newRect . left ;
dy = oldRect . top - newRect . top ;
}
hRatio = newRect . height / ( Math . max ( oldRect . height , 1 ) ) ;
wRatio = isTextNode ? hRatio : newRect . width / ( Math . max ( oldRect . width , 1 ) ) ;
wRatio = isTextNode ? hRatio : newRect . width / ( Math . max ( oldRect . width , 1 ) ) ; //text scales based on hRatio
// Animate new element
origTransform = newStyle . transform ;
origTransition = newStyle . transition ;
if ( newComputedStyle . display === 'inline' ) //inline elements cannot be transformed
newStyle . display = 'inline-block' //this does not appear to have any negative side effects
newStyle . display = 'inline-block' ; //this does not appear to have any negative side effects
newStyle . transition = origTransition + 'transform 0s' ;
newStyle . transformOrigin = isTextNode ? 'center top' : '0 0' ; //made conditional
newStyle . transformOrigin = calcTransformOrigin( isTextNode , newTextRect , newRect ) ;
newStyle . opacity = '0' ;
newStyle . transform = origTransform + 'translate(' + dx + 'px, ' + dy + 'px) ' +
'scale(' + 1 / wRatio + ', ' + 1 / hRatio + ')' ;
@ -69,19 +123,18 @@ function post() {
setNextFrame ( newStyle , 'opacity' , '1' ) ;
// Animate old element
oldStyle . position = 'absolute' ;
oldStyle . top = new Rect. top + 'px' ;
oldStyle . left = new Rect. left + 'px' ;
oldStyle . top = old Rect. top + 'px' ; //start at existing position
oldStyle . left = old Rect. left + 'px' ;
oldStyle . width = oldRect . width + 'px' ; //Needed for elements who were sized relative to their parents
oldStyle . height = oldRect . height + 'px' ; //Needed for elements who were sized relative to their parents
oldStyle . margin = 0 ; //Margin on hero element leads to incorrect positioning
oldStyle . transformOrigin = isTextNode ? 'center top' : '0 0' ; //made conditional
oldStyle . transform = ' translate('+ dx + 'px, ' + dy + 'px) ';
oldStyle . transformOrigin = calcTransformOrigin( isTextNode , oldTextRect , oldRect ) ;
oldStyle . transform = ' ';
oldStyle . opacity = '1' ;
oldStyle . textAlign = oldVnode . savedStyle . textAlign ; //needed when elements have inherited property
// if (oldVnode.savedStyle.display === 'inline')
// oldStyle.display = 'inline-block'
oldStyle . textAlign = oldVnode . savedStyle . textAlign ; //re-apply saved inherited properties
oldStyle . color = oldVnode . savedStyle . color ;
document . body . appendChild ( oldElm ) ;
setNextFrame ( oldStyle , 'transform' , ' scale('+ wRatio + ', ' + hRatio + ')' ) ;
setNextFrame ( oldStyle , 'transform' , ' translate('+ - dx + 'px, ' + - dy + 'px) scale('+ wRatio + ', ' + hRatio + ')' ) ; //scale must be on far right for translate to be correct
setNextFrame ( oldStyle , 'opacity' , '0' ) ;
oldElm . addEventListener ( 'transitionend' , function ( ev ) {
if ( ev . propertyName === 'transform' )