All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.examples.hero.hero.js Maven / Gradle / Ivy

Go to download

A virtual DOM library with focus on simplicity, modularity, powerful features and performance.

The newest version!
const raf =
  (typeof window !== "undefined" && window.requestAnimationFrame) || setTimeout;

const nextFrame = function (fn) {
  raf(function () {
    raf(fn);
  });
};

function setNextFrame(obj, prop, val) {
  nextFrame(function () {
    obj[prop] = val;
  });
}

function getTextNodeRect(textNode) {
  let rect;
  if (document.createRange) {
    const 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
      const relativeCenterX =
        textRect.left + textRect.width / 2 - boundingRect.left;
      const 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;
}

let removed, created;
function pre() {
  removed = {};
  created = [];
}

function create(oldVnode, vnode) {
  const hero = vnode.data.hero;
  if (hero && hero.id) {
    created.push(hero.id);
    created.push(vnode);
  }
}

function destroy(vnode) {
  const hero = vnode.data.hero;
  if (hero && hero.id) {
    const 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
    const computedStyle = window.getComputedStyle(elm, undefined); // get current styles (includes inherited properties)
    vnode.savedStyle = JSON.parse(JSON.stringify(computedStyle)); // save a copy of computed style values
    removed[hero.id] = vnode;
  }
}

function post() {
  let i,
    id,
    newElm,
    oldVnode,
    oldElm,
    hRatio,
    wRatio,
    oldRect,
    newRect,
    dx,
    dy,
    origTransform,
    origTransition,
    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, undefined); // get full computed style for new element
      oldElm = oldVnode.elm;
      oldStyle = oldElm.style;
      // Overall element bounding boxes
      newRect = newElm.getBoundingClientRect();
      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); // 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.transition = origTransition + "transform 0s";
      newStyle.transformOrigin = calcTransformOrigin(
        isTextNode,
        newTextRect,
        newRect
      );
      newStyle.opacity = "0";
      newStyle.transform = `${origTransform}translate(${dx}px, ${dy}px) scale(${
        1 / wRatio
      }, ${1 / hRatio})`;
      setNextFrame(newStyle, "transition", origTransition);
      setNextFrame(newStyle, "transform", origTransform);
      setNextFrame(newStyle, "opacity", "1");
      // Animate old element
      for (const key in oldVnode.savedStyle) {
        // re-apply saved inherited properties
        if (String(parseInt(key)) !== key) {
          const ms = key.substring(0, 2) === "ms";
          const moz = key.substring(0, 3) === "moz";
          const webkit = key.substring(0, 6) === "webkit";
          if (!ms && !moz && !webkit) {
            // ignore prefixed style properties
            oldStyle[key] = oldVnode.savedStyle[key];
          }
        }
      }
      oldStyle.position = "absolute";
      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 = calcTransformOrigin(
        isTextNode,
        oldTextRect,
        oldRect
      );
      oldStyle.transform = "";
      oldStyle.opacity = "1";
      document.body.appendChild(oldElm);
      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") {
          document.body.removeChild(ev.target);
        }
      });
    }
  }
  removed = created = undefined;
}

export const heroModule = { pre, create, destroy, post };




© 2015 - 2025 Weber Informatics LLC | Privacy Policy