Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

javascript/css animation from dom node to dom node

ng-animate-ref allows to create a transition from one dom node to another.

ng-animate uses all the css styles like position, font-size, font-color and more from the first dom element and the second dom element and creates a css 3 animation to move the element from state a to state b.

This is exactly what I need but unfortunately I can't use Angular 1 in the current project.

Is there any reusable way to achieve the same css3 animation without moving all styles from my css files to javascript?

To illustrate the problem please see the following example. As you can see the example has no custom javascript animation code at all but only javascript code which handles the state logic switching elements from list a to b.

The animation definition is written in pure css.

Demo:
https://codepen.io/jonespen/pen/avBZpO/

Preview:
Preview of the demo

like image 694
jantimon Avatar asked Sep 12 '16 10:09

jantimon


People also ask

Which is better for animation CSS or JavaScript?

TL;DR # Use CSS animations for simpler "one-shot" transitions, like toggling UI element states. Use JavaScript animations when you want to have advanced effects like bouncing, stop, pause, rewind, or slow down.

Does CSS animation use JavaScript?

CSS allows animation of HTML elements without using JavaScript or Flash! In this chapter you will learn about the following properties: @keyframes.

How do I add a node to a DOM?

Inserting Nodes into the DOM The methods appendChild() and insertBefore() are used to add items to the beginning, middle, or end of a parent element, and replaceChild() is used to replace an old node with a new node.

Can JavaScript add animation?

Animation CodeJavaScript animations are done by programming gradual changes in an element's style. The changes are called by a timer. When the timer interval is small, the animation looks continuous.


2 Answers

Of course, jQuery animate can achieve it without any plugins.

Maybe there are not many lines of code, but they do have some complexity.

Here is what you want ( ps: jquery-ui only use to change color ).

$(document).ready(function() {    var animating = false,      durtion = 300;    $('.items').on("click", ".items-link", function() {      if (animating) return;      animating = true;      var $this = $(this),        dir = $this.parent().hasClass("items-l") ? "r" : "l",        color = dir == "l" ? "#0000FF" : "#F00000",        index = $this.attr("data-index");        var toItems = $('.items-' + dir),        itemsLinks = toItems.find(".items-link"),        newEle = $this.clone(true),        nextEle = $this.next(),        toEle;        if (itemsLinks.length == 0) {        toItems.append(newEle)      } else {        itemsLinks.each(function() {          if ($(this).attr("data-index") > index) {            toEle = $(this);            return false;          }        });        if (toEle) {          toEle.before(newEle).animate({            "marginTop": $this.outerHeight()          }, durtion, function() {            toEle.css("marginTop", 0);          });        } else {          toEle = itemsLinks.last();          toEle.after(newEle)        }      }        nextEle && nextEle.css("marginTop", $this.outerHeight())        .animate({          "marginTop": 0        }, durtion);        var animate = newEle.position();      animate["background-color"] = color;      newEle.hide() && $this.css('position', 'absolute')        .animate(animate, durtion, function() {          newEle.show();          $this.remove();          animating = false;        });    });  });
.items {    padding: 0;    -webkit-transition: 300ms linear all;    transition: 300ms linear all;  }  .items.items-l {    float: left  }  .items.items-r {    float: right  }  .items.items-l a {    background: #0000FF  }  .items.items-r a {    background: #F00000  }  .items a,  .items-link {    color: #fff;    padding: 10px;    display: block;  }  .main {    width: 100%;  }
<script type="text/javascript" src="//code.jquery.com/jquery-1.9.1.js">  </script>  <script type="text/javascript" src="//code.jquery.com/ui/1.9.2/jquery-ui.js">  </script>  <link rel="stylesheet" type="text/css" href="//code.jquery.com/ui/1.9.2/themes/base/jquery-ui.css">  <div class="main">    <div class="items items-l">      <a class="items-link" data-index="1" href="#">Item 1</a>      <a class="items-link" data-index="2" href="#">Item 2</a>      <a class="items-link" data-index="3" href="#">Item 3</a>       <a class="items-link" data-index="4" href="#">Item 4</a>      </div>      <div class="items items-r">        <a href="#" class="items-link" data-index="5">Item 5</a>        <a href="#" class="items-link" data-index="6">Item 6</a>        <a href="#" class="items-link" data-index="7">Item 7</a>        <a href="#" class="items-link" data-index="8">Item 8</a>    </div>    
like image 184
TTCC Avatar answered Sep 24 '22 12:09

TTCC


A plain javascript solution that uses:

  • HTMLElement.getBoundingClientRect to find differences between the old and new positions of element
  • css transition to animate
  • css transform to translate

Explanation of approach:

The core idea is to have the browser only calculate/reflow the DOM once. We'll take care of the transition between the initial state and this new one ourselves.

By only transitioning (a) the GPU accelerated transform property, on (b) a small selection of elements (all <li> elements), we'll try to ensure a high frame rate.

// Store references to DOM elements we'll need:  var lists = [    document.querySelector(".js-list0"),    document.querySelector(".js-list1")  ];  var items = Array.prototype.slice.call(document.querySelectorAll("li"));    // The function that triggers the css transitions:  var transition = (function() {     var keyIndex = 0,        bboxesBefore = {},        bboxesAfter = {},        storeBbox = function(obj, element) {          var key = element.getAttribute("data-key");          if (!key) {            element.setAttribute("data-key", "KEY_" + keyIndex++);            return storeBbox(obj, element);          }                    obj[key] = element.getBoundingClientRect();        },        storeBboxes = function(obj, elements) {          return elements.forEach(storeBbox.bind(null, obj));        };        // `action` is a function that modifies the DOM from state *before* to state *after*    // `elements` is an array of HTMLElements which we want to monitor and transition    return function(action, elements) {      if (!elements || !elements.length) {        return action();      }            // Store old position      storeBboxes(bboxesBefore, elements);            // Turn off animation      document.body.classList.toggle("animated", false);            // Call action that moves stuff around      action();            // Store new position      storeBboxes(bboxesAfter, elements);            // Transform each element from its new position to its old one      elements.forEach(function(el) {        var key = el.getAttribute("data-key");        var bbox = {          before: bboxesBefore[key],          after: bboxesAfter[key]        };                var dx = bbox.before.left - bbox.after.left;        var dy = bbox.before.top - bbox.after.top;                el.style.transform = "translate3d(" + dx + "px," + dy + "px, 0)";      });        // Force repaint      elements[0].parentElement.offsetHeight;        // Turn on CSS animations      document.body.classList.toggle("animated", true);           // Remove translation to animate to natural position      elements.forEach(function(el) {        el.style.transform = "";      });    };  }());    // Event handler & sorting/moving logic  document.querySelector("div").addEventListener("click", function(e) {    var currentList = e.target.getAttribute("data-list");    if (currentList) {      var targetIndex = e.target.getAttribute("data-index");      var nextIndex = 0;        // Get the next list from the lists array      var newListIndex = (+currentList + 1) % lists.length;      var newList = lists[newListIndex];            for (nextIndex; nextIndex < newList.children.length; nextIndex++) {        if (newList.children[nextIndex].getAttribute("data-index") > targetIndex) {          break;        }      }            // Call the transition      transition(function() {        newList.insertBefore(e.target, newList.children[nextIndex]);        e.target.setAttribute("data-list", newListIndex);      }, items);    }  });
div { display: flex; justify-content: space-between; }      .animated li {    transition: transform .5s ease-in-out;  }
<h2>Example</h2>  <div>    <ul class="js-list0">      <li data-index="0" data-list="0">Item 1</li>      <li data-index="3" data-list="0">Item 2</li>      <li data-index="5" data-list="0">Item 4</li>      <li data-index="7" data-list="0">Item 6</li>    </ul>      <ul class="js-list1">      <li data-index="4" data-list="1">Item 3</li>      <li data-index="6" data-list="1">Item 5</li>    </ul>  </div>

Edit:

To add support for other properties you'd like to animate, follow this 4 step approach:

  1. Add the css rule to the .animated transition property:

    transition: transform .5s ease-in-out,             background-color .5s ease-in-out; 
  2. Store the properties computed style before you modify the DOM:

    obj[key].bgColor = window   .getComputedStyle(element, null)   .getPropertyValue("background-color"); 
  3. After modifying, quickly set a temporary override for the property, like we already did for the transform prop.

    el.style.backgroundColor = bbox.before.bgColor; 
  4. After turning on the css animations, remove the temporary override to trigger the css transition:

    el.style.backgroundColor = ""; 

In action: http://codepen.io/anon/pen/pELzdr

Please note that css transitions work very well on some properties, like transform and opacity, while they might perform worse on others (like height, which usually triggers repaints). Make sure you monitor your frame rates to prevent performance issues!

like image 43
user3297291 Avatar answered Sep 24 '22 12:09

user3297291