Reputation: 38150
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/
Upvotes: 33
Views: 3285
Reputation: 142
I was inspired by all the great previous posts and turned it into a library which allows to use ng-animate without angular.
The library is called Animorph
I solved the described example with almost no custom javascript code (as the heavy parts are all in the library).
Please note that right now it doesn't sort the lists but is focused only on the animation part.
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;
}
Upvotes: 1
Reputation: 23382
A plain javascript solution that uses:
HTMLElement.getBoundingClientRect
to find differences between the old and new positions of elementtransition
to animatetransform
to translateThe 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:
Add the css rule to the .animated
transition
property:
transition: transform .5s ease-in-out,
background-color .5s ease-in-out;
Store the properties computed style before you modify the DOM:
obj[key].bgColor = window
.getComputedStyle(element, null)
.getPropertyValue("background-color");
After modifying, quickly set a temporary override for the property, like we already did for the transform
prop.
el.style.backgroundColor = bbox.before.bgColor;
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!
Upvotes: 11
Reputation: 5291
Because you already use jQuery, my answer was quite easy to make
$(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>
The CSS makes sure that elements with class left
are on the left, and the ones with class right
are on the right, but because of the following two lines
left: 100%;
transform: translateX(-100%);
the left and transform value will be transitioned, but it will look as if right
is set to 0.
The script recalculates everything on 3 occasions
When you click one of the items, it will simply toggle it's class from left
to right
. After that, the recalculation is being done. It keeps a variable data
, which keeps track of how high each column has gotten with each item that's in it, and moves every one after that that much from the top.
This script can account for elements with margin, padding, multiple lines and images, if you want.
Also, the list has a class wait
, which hides all the elements until they're set for the first time. This prevents the user from seeing the items when they're not yet placed.
Hope this helps
Upvotes: 4
Reputation: 1055
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>
Upvotes: 18
Reputation: 8409
Just copy paste this to your HTML
page, this is exactly what you need
/* 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>
Upvotes: 0