I’m working on a routine that reorders a set of div on an HTML page using JavaScript. The re-ordering is working correctly.
To add some visual feedback when the div moves, I’m using a CSS animation (and keyframes) class that gets added
Each div has an up and down arrow with an event listener.
The "Up" functionality works every time. The "Down" functionality works exactly once, unless the Up button is used, in which case the Down functionality is reset and works just once again.
The entire experiment is in a self-contained PHP file:
<?php
ini_set('display_errors', 1);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<title>Reorder List</title>
<style>
.component {
margin-bottom: 4px;
font-size: 16px;
border: 1px solid black;
border-radius: 5px;
}
.component button {
margin: 2px;
padding: 2px;
border-radius: 5px;
border: 1px solid grey;
font-size: 24px;
}
.component span {
margin-left: 10px;
}
.container {
border: 1px solid red;
padding: 5px;
border-radius: 5px;
}
.internal {
clear: both;
display: block;
margin-left: 15px;
}
.fade-in {
animation: fadeIn 1s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>
</head>
<body>
<p>[<a href="/experiments/">Experiments Home</a>]</p>
<p>This page demonstrates: </p>
<ol>
<li>Reordering child divs of a parent div using JavaScript.</li>
<li>The order set by JavaScript is retained in a POST request.</li>
</ol>
<?php
$colors = [
"red" => "RED",
"green" => "GREEN",
"cyan" => "CYAN",
"darkmagenta" => "DARK MAGENTA",
"blue" => "BLUE",
"darksalmon" => "DARK SALMON",
"lightcoral" => "LIGHT CORAL",
"mediumspringgreen" => "MEDIUM SPRING GREEN",
"indigo" => "INDIGO"
];
if ( $_POST ) {
$colorlist = $_POST;
} else {
$colorlist = $colors;
} // end else
?>
<form method="post" action="reorder_list.php">
<div class="container" id="list">
<?php
$i = 0;
foreach ($colorlist as $key => $value) {
$i++;
?>
<div class="component">
<button class="up material-icons">arrow_upward</button>
<button class="down material-icons">arrow_downward</button>
<input type="hidden" name="<?= $key ?>" value="<?= $value ?>">
<p class="internal" style="color: <?= $key ?>"><?= $i . ". " . $value ?></p>
<p class="internal">Here is another paragraph</p>
<p class="internal"><label for="name">Here is a form element</label> <input type="text" id="name" name="name"></p>
<p class="internal">And yet another paragraph</p>
</div>
<?php
} // end foreach ?>
</div>
<p><input type="submit"></p>
</form>
<script>
document.querySelectorAll('.up').forEach(button => {
button.addEventListener('click', function(event) {
once: true;
event.preventDefault();
const el = button.parentElement;
const prevEl = el.previousElementSibling;
if (prevEl) {
el.parentNode.insertBefore(el, prevEl);
//el.classList.remove('fade-in');
el.classList.add('fade-in');
//setTimeout(el.classList.remove('fade-in'), 3000);
}
});
});
document.querySelectorAll('.down').forEach(button => {
button.addEventListener('click', function(event) {
once: true;
event.preventDefault();
const el = button.parentElement;
const nextEl = el.nextElementSibling;
if (nextEl) {
el.parentNode.insertBefore(nextEl, el);
//el.classList.remove('fade-in');
el.classList.add('fade-in');
//setTimeout(el.classList.remove('fade-in'), 3000);
}
});
});
</script>
<h1>Original Order</h1>
<ul id="list">
<?php
$i = 0;
foreach ($colors as $key => $value) {
$i++;
?>
<li class="list-item"><span style="color: <?= $key; ?>"><?= $i . ". " . $value ?></span></li>
<?php
}
?>
</ul>
</body>
</html>
I have tried using setTimeOut to remove the animation class which disables the Up and Down functionally completely. (You can see the attempt commented out in the code)
I also tried removing the animation class before adding it back in.
I tried adding once: true to the event
The goal is to get both Up and Down functionality working consistently.
2
Answers
Issue:
The issue with your animation class is that the CSS animation doesn’t re-trigger if the class is re-applied before the animation completes. CSS animations run once when a class is applied, and reapplying the class without removing it doesn’t restart the animation.
Solution:
You need to remove the fade-in class before re-adding it, ensuring the animation restarts each time.
Good luck!
I created a minimal reproducible example from your code and it successfully does the up and down movement even repeatedly. However, the animation works only and exactly once either way. If you do n moves downwards, only the first animates, until you do m move upwards, only the first animates, then the next down animates, etc.:
I presume that this animation issue is the issue you intend to fix. Therefore:
What the problem was? You move HTML elements, that is, you destroy and recreate them. Which means that by the time your new element, the copy is created the second time, it will already have the fade in animation without the animation itself. This is why instead I am searching for the new node instead and add the class to it and then remove the class from it.