Une solution javascript simple qui utilise :
-
HTMLElement.getBoundingClientRect
pour trouver des différences entre les anciennes et les nouvelles positions de l'élément
- css
transition
d'animer
- css
transform
pour traduire
Explication de l'approche :
L'idée de base est de faire en sorte que le navigateur calcule/remplit uniquement le DOM une fois . Nous nous occuperons nous-mêmes de la transition entre l'état initial et ce nouvel état.
En n'effectuant que la transition (a), le GPU accéléré transform
de la propriété, sur (b) une petite sélection d'éléments (tous <li>
éléments), nous essaierons d'assurer une fréquence d'images élevée.
// 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 :
Pour ajouter la prise en charge d'autres propriétés que vous souhaitez animer, suivez cette approche en 4 étapes :
-
Ajoutez la règle css à l'élément .animated
transition
propriété :
transition: transform .5s ease-in-out,
background-color .5s ease-in-out;
-
Stockez le style calculé des propriétés avant de modifier le DOM :
obj[key].bgColor = window
.getComputedStyle(element, null)
.getPropertyValue("background-color");
-
Après l'avoir modifiée, définissez rapidement une surcharge temporaire pour la propriété, comme nous l'avons déjà fait pour la propriété transform
prop.
el.style.backgroundColor = bbox.before.bgColor;
-
Après avoir activé les animations css, supprimez la dérogation temporaire permettant de déclencher la transition css :
el.style.backgroundColor = "";
En action : http://codepen.io/anon/pen/pELzdr
Veuillez noter que les transitions css fonctionnent très bien sur certaines propriétés, telles que transform
et opacity
alors qu'ils peuvent être moins performants pour d'autres (comme le height
qui déclenche généralement des repeints). Veillez à surveiller vos fréquences d'images pour éviter les problèmes de performances !