从 dom 节点到 dom 节点的 javascript/css 动画

Posted

技术标签:

【中文标题】从 dom 节点到 dom 节点的 javascript/css 动画【英文标题】:javascript/css animation from dom node to dom node 【发布时间】:2017-01-19 18:35:57 【问题描述】:

ng-animate-ref 允许创建从一个 dom 节点到另一个的过渡

ng-animate 使用第一个 dom 元素和第二个 dom 元素的所有 css 样式,如 positionfont-sizefont-color 等,并创建一个 css 3 动画以将元素从状态 a 移动声明b

这正是我需要的,但不幸的是我不能在当前项目中使用 Angular 1。

是否有任何可重用的方法来实现相同的 css3 动画,而无需将所有样式从我的 css 文件移动到 javascript

为了说明问题,请参阅以下示例。 如您所见,该示例完全没有自定义 javascript 动画代码,只有处理从列表 ab 的状态逻辑切换元素的 javascript 代码。

动画定​​义是用纯CSS编写的

演示:https://codepen.io/jonespen/pen/avBZpO/

预览:

【问题讨论】:

我使用 vanilla jQuery 做了一些类似于你想要的 here 的东西。我用this post 来制作它。它没有您想要的一切(例如褪色和中心项目移开),但它不是一个糟糕的 80/20 解决方案。明天我会花更多的时间。 @Danman 也许***.com/questions/4493449/… 可能会有所帮助,但我更喜欢带有单元测试的库而不是快速的 jquery sn-p.. 要求我们推荐或查找书籍、工具、软件库、教程或其他非现场资源的问题对于 Stack Overflow 来说是题外话,因为它们往往会吸引固执己见答案和垃圾邮件。相反,请描述问题以及迄今为止为解决该问题所做的工作。 @jantimon 您能否在问题中包含您尝试解决问题的内容? “但我更喜欢带有单元测试的库而不是快速 jquery sn-p” 为什么需要一个库来实现需求?虽然如果使用库是必需的,为什么使用 jQuery 返回预期结果不适用? @jantimon 同时,问题中没有出现“纯css”方法来展示您在哪里尝试解决自己的问题? 【参考方案1】:

当然jQuery animate不用任何插件也可以实现。

也许代码行数不多,但确实有些复杂。

Here是你想要的(ps:jquery-ui只用于改变颜色)。

$(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>
  

【讨论】:

我想将样式保留在 css 中,而不是将它们移动到 javascript 中。这也可能吗?是否可以不添加整个 jquery 和 jquery ui 库?我也更喜欢css3动画而不是js动画..就像在codepen示例中一样 @jantimon 原来的问题没有提到要求是只使用css?事实上,你确定javascript没有用在显示预期效果的链接代码笔上吗? 它仅将javascript用于状态逻辑而不用于动画逻辑。这也是我想要实现的目标,但没有 Angular 1 @jantimon “它仅将javascript用于状态逻辑而不用于动画逻辑。”“状态逻辑”是什么意思? .push().splice().indexOf() 如何影响“状态逻辑”? “也更喜欢 css3 动画而不是 js 动画.. 就像在 codepen 示例中一样” 参见 codepen 的右侧面板; Question 本身的示例使用javascript。您能否将仅使用css 解决问题的尝试包括在内? @guest271314 在我看来,你不是想帮忙,只是想告诉我我错了。 TTCC 因其出色的回答而获得了 120 的声誉,但这不是我想要的。如果您真的在寻找建设性的讨论,请联系我chat.***.com/rooms/17/javascript【参考方案2】:

一个普通的 javascript 解决方案,它使用:

htmlElement.getBoundingClientRect查找元素新旧位置的差异 css transition 动画 css transform 翻译

方法说明:

核心思想是让浏览器只计算/重排DOM一次。我们将自己处理初始状态和新状态之间的转换。

通过仅转换 (a) GPU 加速的transform 属性,(b) 一小部分元素(所有 &lt;li&gt; 元素),我们将尝试确保高帧速率。

// 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>

编辑:

要添加对您想要制作动画的其他属性的支持,请遵循以下 4 步方法:

    将css规则添加到.animatedtransition属性:

    transition: transform .5s ease-in-out,
                background-color .5s ease-in-out;
    

    在修改 DOM 之前存储属性计算样式:

    obj[key].bgColor = window
      .getComputedStyle(element, null)
      .getPropertyValue("background-color");
    

    修改后,快速为属性设置一个临时覆盖,就像我们已经为 transform 属性所做的那样。

    el.style.backgroundColor = bbox.before.bgColor;
    

    开启css动画后,去掉临时覆盖触发css过渡:

    el.style.backgroundColor = "";
    

实际操作中:http://codepen.io/anon/pen/pELzdr

请注意,css 过渡在某些属性上效果很好,例如 transformopacity,而在其他属性上效果可能会更差(例如 height,这通常会触发重绘)。确保监控帧速率以防止出现性能问题!

【讨论】:

这看起来真的很令人印象深刻——它还包括颜色变化吗?你能提供一个codepen吗? 目前不包括颜色变化。我已经对其进行了一些重构,使其更通用。在这个 codepen 中,我展示了如何包含颜色支持:codepen.io/anon/pen/pELzdr 基础知识:在 DOM 修改之前存储计算的背景颜色,为属性添加 css 过渡,在过渡元素上设置临时覆盖以动画到新元素状态。寻找说 Background color support 的 cmets 以在代码中看到它。请注意,背景颜色的变化会更多地影响您的帧速率,因为它们不是 GPU 加速的(如果我没记错的话) 我还在回答中包含了有关如何在编辑中支持其他属性的说明。 如果您在第一个动画完成之前开始第二个动画,似乎缓动被重置 @jantimon 是的。这是想要在 CSS 中管理转换的缺点之一。每次变更的过渡时间都是固定的;无论您将元素移动 1 像素还是 100 像素,它总是需要相同的时间。缓动也是如此:如果你在半过渡中改变方向,一个新的将开始。如果您想支持中间过渡更新,我相信您将不得不远离 CSS。你最终会得到一个你独立更新的转换列表。我想这是一个 100% 完美的复杂解决方案或一个相当简单的 80% 实现之间的权衡......【参考方案3】:

因为你已经用过 jQuery,所以我的回答很简单

$(function()
  var move = function()
    var data = [0,0]
    $('.items > li').each(function()
      var $this = $(this)
      var height = $this.outerHeight(true)
      var side = ($this.hasClass('left') ? 0 : 1)
      $this.css('top', data[side])
      data[side]+=height
    )
  
  $(window).on('resize', function()
    move()
  )
  $(document).on('click', '.items > li', function()
    $(this).toggleClass('left').toggleClass('right')
    move()
  )
  move()
  $('.items').removeClass('wait')
)
.items
  margin: 0;
  padding: 0;
  list-style: none;


.items > li
  display: table;
  position: absolute;
  padding: 10px;
  color: #fff;
  cursor: pointer;
  -webkit-user-select: none;
          user-select: none;
  transition: .3s ease;


.items.wait > li
  visibility: hidden;


.items .left
  left: 0;
  background-color: #1ABC9C;


.items .right
  left: 100%;
  transform: translateX(-100%);
  background-color: #E74C3C;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<ul class="items wait">
  <li class="left">Item 1<br />With some additional text</li>
  <li class="left">Item 2</li>
  <li class="left">Item 3</li>
  <li class="left">Item 4</li>
  <li class="right">Item 5</li>
  <li class="right">Item 6</li>
  <li class="right">Item 7</li>
  <li class="right">Item 8</li>
</ul>

CSS 确保 left 类的元素在左侧,right 类的元素在右侧,但由于以下两行

left: 100%;
transform: translateX(-100%);

left 和 transform 值将被转换,但看起来好像 right 设置为 0。

脚本在 3 次重新计算所有内容

文档准备就绪 窗口大小调整 当其中一项被点击时

当您单击其中一项时,它会简单地将其类别从left 切换到right。之后,将进行重新计算。它保留了一个变量data,该变量跟踪每列与其中的每个项目的高度,并在此之后从顶部移动每一个。

如果需要,此脚本可以考虑具有边距、内边距、多行和图像的元素。

此外,列表有一个类wait,它隐藏所有元素,直到它们第一次被设置。这可以防止用户在项目尚未放置时看到它们。

希望对你有帮助

【讨论】:

我不想更改标记 - 标记只是一种更复杂标记的示例,我在不同包装器之间移动元素并需要对其进行动画处理。 所以你真的希望元素交换父元素,是这样吗?【参考方案4】:

我受到之前所有精彩帖子的启发,并将其变成了一个库,允许在没有角度的情况下使用 ng-animate。

这个库叫做Animorph

我解决了所描述的示例,几乎没有自定义 javascript 代码(因为重的部分都在库中)。

请注意,现在它不对列表进行排序,而是只关注动画部分。

Codepen:http://codepen.io/claudiobmgrtnr/pen/NRrYaQ

Javascript:

  $(".left").on("click", "li.element", function() 
    $(this).amAppendTo('.right', 
      addClasses: ['element--green'],
      removeClasses: ['element--golden']
    );
  );
  $(".right").on("click", "li.element", function() 
    $(this).amPrependTo('.left', 
      addClasses: ['element--golden'],
      removeClasses: ['element--green']
    );
  );

SCSS:

body 
  margin: 0;
  width: 100%;
  &:after 
    content: '';
    display: table;
    width: 100%;
    clear: both;
  


ul 
  list-style-type: none;
  padding: 0;


.element 
  width: 100px;
  height: 30px;
  line-height: 30px;
  padding: 8px;
  list-style: none;
  &--golden 
    background: goldenrod;
  
  &--green 
    background: #bada55;
  
  &.am-leave 
    visibility: hidden;
  
  &.am-leave-prepare 
    visibility: hidden;
  
  &.am-leave-active 
    height: 0;
    padding-top: 0;
    padding-bottom: 0;
  
  &.am-enter 
    visibility: hidden;
  
  &.am-enter-prepare 
    height: 0;
    padding-top: 0;
    padding-bottom: 0;
  
  &.am-enter-active 
    height: 30px;
    padding-top: 8px;
    padding-bottom: 8px;
  
  &.am-enter,
  &.am-move,
  &.am-leave 
    transition: all 300ms;
  


.left 
  float: left;


.right 
  float: right;

【讨论】:

哇,这和我要找的很接近 - 我必须使用 am 前缀吗? 很高兴你喜欢它。不,名称空间是完全可配置的。 – 我会尽快更新文档并实施更多示例 干得好,克劳迪奥!您能否解释一下您的方法以及它与其他答案的不同之处?如果我理解正确,您将复制节点以具有 (1) 动画到隐藏显示模式的节点,(2) 动画到新位置的节点,以及 (3) 清除空间的节点新职位?这是否需要通过 css 提升固定高度? 谢谢,很高兴听到你喜欢它。我的方法深受来自 angular.js 的 ng-animate 原理的启发。没错,它创建了两个克隆,并使用一个作为占位符,另一个作为动画元素。如果要为元素的高度设置动画,则必须使用固定高度(目前)。至少在动画运行时。【参考方案5】:

只需将其复制粘贴到您的HTML 页面,这正是您所需要的

/* CSS */

#sortable 
	list-style-type: none;
	margin: 0;
	padding: 0;
	width: 500px;

#sortable li 
	margin: 0 125px 0 3px;
	padding: 0.4em;
	font-size: 1.4em;
	height: 18px;
	float: left;
	color: #fff;
	cursor: pointer;

#sortable li:nth-child(odd) 
	background: #01BC9C;

#sortable li:nth-child(even) 
	background: #E54A2D;

#sortable li span 
	position: absolute;
	margin-left: -1.3em;
<!-- HTML -->

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Try with this</title>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" href="/resources/demos/style.css">

<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
  $( function() 
    $( "#sortable" ).sortable();
    $( "#sortable" ).disableSelection();
   );
  </script>
</head>
<body>
<ul id="sortable">
  <li class="ui-state-default">Item 1</li>
  <li class="ui-state-default">Item 2</li>
  <li class="ui-state-default">Item 3</li>
  <li class="ui-state-default">Item 4</li>
  <li class="ui-state-default">Item 5</li>
  <li class="ui-state-default">Item 6</li>
  <li class="ui-state-default">Item 7</li>
</ul>
</body>
</html>

【讨论】:

不幸的是,这并不是他想要的。有关更多详细信息,请参阅他包含的演示 (codepen.io/jonespen/pen/avBZpO?editors=0100) 我不是在寻找拖放。我正在寻找动画的抽象 - 只需设置两个状态的样式并在两者之间设置动画。

以上是关于从 dom 节点到 dom 节点的 javascript/css 动画的主要内容,如果未能解决你的问题,请参考以下文章

DOM节点:操作节点

如何在 Java 中将 DOM 节点从一个文档复制到另一个文档?

如何将事件附加到 DOM 的所有节点? [关闭]

使用 SimpleXML/DOM 通过 php 将节点从一个 XML 复制到另一个

如何从 DOM 节点转到 viewModel 对象?

监听DOM变化