From 1a45b9dcfdd094bb33ae5b1ff854389218d40ca0 Mon Sep 17 00:00:00 2001 From: Mike Montoya Date: Mon, 28 Dec 2015 22:07:03 +0000 Subject: [PATCH] Now works with hero text that changes size and alignment. --- modules/hero.js | 97 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 22 deletions(-) diff --git a/modules/hero.js b/modules/hero.js index 9235c7f..ccf3fad 100644 --- a/modules/hero.js +++ b/modules/hero.js @@ -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 = newRect.top + 'px'; - oldStyle.left = newRect.left + 'px'; + oldStyle.top = oldRect.top + 'px'; //start at existing position + oldStyle.left = oldRect.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')