从 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 样式,如 position
、font-size
、font-color
等,并创建一个 css 3 动画以将元素从状态 a
移动声明b
。
这正是我需要的,但不幸的是我不能在当前项目中使用 Angular 1。
是否有任何可重用的方法来实现相同的 css3 动画,而无需将所有样式从我的 css 文件移动到 javascript?
为了说明问题,请参阅以下示例。
如您所见,该示例完全没有自定义 javascript 动画代码,只有处理从列表 a
到 b
的状态逻辑切换元素的 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) 一小部分元素(所有 <li>
元素),我们将尝试确保高帧速率。
// 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规则添加到.animated
transition
属性:
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 过渡在某些属性上效果很好,例如 transform
和 opacity
,而在其他属性上效果可能会更差(例如 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 动画的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Java 中将 DOM 节点从一个文档复制到另一个文档?