Das Animieren von Elementen mit CSS kann entweder sehr einfach oder sehr schwierig sein, je nachdem, was Sie tun möchten. Ändern Sie die Hintergrundfarbe eines Buttons, wenn Sie mit der Maus darüber fahren? Einfach. Animieren Sie die Position und Größe eines Elements auf performante Weise, die auch die Position anderer Elemente beeinflusst? Knifflig! Genau damit werden wir uns in diesem Artikel beschäftigen.
Ein gängiges Beispiel ist das Entfernen eines Elements aus einem Stapel von Elementen. Die darüber liegenden Elemente müssen nach unten fallen, um den Platz eines Elements auszugleichen, das vom unteren Rand des Stapels entfernt wurde. So verhalten sich Dinge im wirklichen Leben, und Benutzer erwarten möglicherweise eine solche lebensnahe Bewegung auf einer Website. Wenn dies nicht geschieht, kann der Benutzer verwirrt oder kurzzeitig desorientiert sein. Man erwartet, dass sich etwas basierend auf Lebenserfahrung auf eine Weise verhält, und erhält etwas völlig anderes, und Benutzer benötigen möglicherweise zusätzliche Zeit, um die unrealistische Bewegung zu verarbeiten.
Hier ist eine Demonstration einer Benutzeroberfläche zum Hinzufügen von Elementen (klicken Sie auf den Button) oder zum Entfernen von Elementen (klicken Sie auf das Element).
Sie könnten die schlechte Benutzeroberfläche leicht kaschieren, indem Sie eine "Fade-Out"-Animation oder etwas Ähnliches hinzufügen, aber das Ergebnis wird nicht besonders gut sein, da die Liste abrupt einklappt und dieselben kognitiven Probleme verursacht.
Das Anwenden von reinen CSS-Animationen auf ein dynamisches DOM-Ereignis (Hinzufügen brandneuer Elemente und vollständiges Entfernen von Elementen) ist extrem knifflige Arbeit. Wir werden dieses Problem direkt angehen und drei sehr unterschiedliche Arten von Animationen durchgehen, die dies handhaben und alle das gleiche Ziel verfolgen, Benutzern dabei zu helfen, Änderungen an einer Liste von Elementen zu verstehen. Bis wir fertig sind, werden Sie gerüstet sein, diese Animationen zu verwenden oder eigene basierend auf den Konzepten zu erstellen.
Wir werden auch auf Barrierefreiheit eingehen und wie aufwendige HTML-Layouts mit Hilfe von ARIA-Attributen eine gewisse Kompatibilität mit Barrierefreiheitsgeräten beibehalten können.
Die Slide-Down-Opacity-Animation
Ein sehr moderner Ansatz (und mein persönlicher Favorit) ist, wenn neu hinzugefügte Elemente vertikal in ihre Position ein- und ausblenden, je nachdem, wo sie landen werden. Das bedeutet auch, dass die Liste einen Platz "öffnen" muss (ebenfalls animiert), um Platz dafür zu schaffen. Wenn ein Element die Liste verlässt, muss der von ihm eingenommene Platz schrumpfen.
Da wir so viele verschiedene Dinge gleichzeitig tun, müssen wir unsere DOM-Struktur ändern, um jedes .list-item in eine Container-Klasse namens .list-container zu verpacken. Dies ist absolut unerlässlich, damit unsere Animation funktioniert.
<ul class="list">
<li class="list-container">
<div class="list-item">List Item</div>
</li>
<li class="list-container">
<div class="list-item">List Item</div>
</li>
<li class="list-container">
<div class="list-item">List Item</div>
</li>
<li class="list-container">
<div class="list-item">List Item</div>
</li>
</ul>
<button class="add-btn">Add New Item</button>
Die Gestaltung ist hier unorthodox, denn um unseren Animationseffekt später zu erzielen, müssen wir unsere Liste auf eine sehr spezifische Weise gestalten, die das Problem löst, allerdings auf Kosten einiger üblicher CSS-Praktiken.
.list {
list-style: none;
}
.list-container {
cursor: pointer;
font-size: 3.5rem;
height: 0;
list-style: none;
position: relative;
text-align: center;
width: 300px;
}
.list-container:not(:first-child) {
margin-top: 10px;
}
.list-container .list-item {
background-color: #D3D3D3;
left: 0;
padding: 2rem 0;
position: absolute;
top: 0;
transition: all 0.6s ease-out;
width: 100%;
}
.add-btn {
background-color: transparent;
border: 1px solid black;
cursor: pointer;
font-size: 2.5rem;
margin-top: 10px;
padding: 2rem 0;
text-align: center;
width: 300px;
}
Wie man Abstände handhabt
Zuerst verwenden wir margin-top, um vertikalen Abstand zwischen den Elementen im Stapel zu schaffen. Es gibt keinen unteren Rand, damit die anderen Listenelemente die Lücke füllen können, die durch das Entfernen eines Listenelements entsteht. So hat es immer noch einen unteren Rand, auch wenn wir die Containerhöhe auf null gesetzt haben. Dieser zusätzliche Abstand wird zwischen dem Listenelement geschaffen, das sich direkt unter dem gelöschten Listenelement befand. Und dasselbe Listenelement sollte sich als Reaktion auf die Containerhöhe von null des gelöschten Listenelements nach oben bewegen. Und da dieser zusätzliche Abstand den vertikalen Abstand zwischen den Listenelementen weiter vergrößert, als wir wollen. Deshalb verwenden wir margin-top, um das zu verhindern.
Das tun wir aber nur, wenn der betreffende Elementcontainer nicht der erste in der Liste ist. Deshalb haben wir :not(:first-child) verwendet – es zielt auf alle Container außer dem allerersten ab (ein aktivierender Selektor). Das tun wir, weil wir nicht möchten, dass das allererste Listenelement vom oberen Rand der Liste nach unten gedrückt wird. Wir möchten, dass dies nur bei jedem nachfolgenden Element geschieht, da diese direkt unter einem anderen Listenelement positioniert sind, während das erste es nicht ist.
Das wird wahrscheinlich noch keinen vollständigen Sinn ergeben, da wir im Moment keine Elemente auf Höhe Null setzen. Das werden wir aber später tun, und um den vertikalen Abstand zwischen den Listenelementen korrekt einzustellen, müssen wir den Rand so setzen, wie wir es tun.
Ein Hinweis zur Positionierung
Etwas anderes, das erwähnenswert ist, ist die Tatsache, dass die .list-item Elemente, die in den übergeordneten .list-container Elementen verschachtelt sind, eine position von absolute haben, was bedeutet, dass sie außerhalb des DOM positioniert sind und sich relativ zu ihren relativ positionierten .list-container Elementen befinden. Dies tun wir, um das .list-item Element beim Entfernen nach oben schweben zu lassen und gleichzeitig die anderen .list-item Elemente dazu zu bringen, die Lücke zu füllen, die das Entfernen dieses .list-item Elements hinterlassen hat. Wenn dies geschieht, kollabiert das .list-container Element, das nicht absolute positioniert ist und daher vom DOM betroffen ist, seine Höhe, wodurch die anderen .list-container Elemente seinen Platz einnehmen können, und das .list-item Element – das absolute positioniert ist – schwebt nach oben, beeinflusst aber nicht die Struktur der Liste, da es nicht vom DOM betroffen ist.
Höhe handhaben
Leider haben wir noch nicht genug getan, um eine ordnungsgemäße Liste zu erhalten, bei der die einzelnen Listenelemente nacheinander übereinander gestapelt sind. Stattdessen sehen wir im Moment nur ein einziges .list-item, das alle Listenelemente übereinander auf denselben Platz stapelt. Das liegt daran, dass die .list-item Elemente zwar durch ihre padding-Eigenschaft eine gewisse Höhe haben mögen, ihre Elternelemente jedoch nicht, sondern stattdessen eine Höhe von Null haben. Das bedeutet, dass wir im DOM nichts haben, das diese Elemente voneinander trennt, denn um das zu tun, bräuchten wir, dass unsere .list-item Container eine gewisse Höhe haben, da sie im Gegensatz zu ihren Kindelementen vom DOM betroffen sind.
Um die Höhe unserer Listencontainer perfekt an die Höhe ihrer Kindelemente anzupassen, benötigen wir JavaScript. Wir speichern also alle unsere Listenelemente in einer Variablen. Dann erstellen wir eine Funktion, die sofort aufgerufen wird, sobald das Skript geladen ist.
Dies wird die Funktion, die die Höhe der Listenelementcontainer handhabt
const listItems = document.querySelectorAll('.list-item');
function calculateHeightOfListContainer(){
};
calculateHeightOfListContainer();
Als Erstes extrahieren wir das allererste .list-item Element aus der Liste. Das können wir tun, weil sie alle die gleiche Größe haben, also spielt es keine Rolle, welches wir verwenden. Sobald wir Zugriff darauf haben, speichern wir seine Höhe in Pixeln über die clientHeight-Eigenschaft des Elements. Danach erstellen wir ein neues <style>-Element, das unmittelbar danach dem body des Dokuments vorangestellt wird, damit wir direkt eine CSS-Klasse erstellen können, die den gerade extrahierten Höhenwert enthält. Und mit diesem <style>-Element sicher im DOM schreiben wir eine neue .list-container-Klasse mit Stilen, die automatisch Vorrang vor den in der externen Stylesheet deklarierten Stilen haben, da diese Stile aus einem tatsächlichen <style>-Tag stammen. Das gibt den .list-container-Klassen die gleiche Höhe wie ihren .list-item-Kindern.
const listItems = document.querySelectorAll('.list-item');
function calculateHeightOfListContainer() {
const firstListItem = listItems[0];
let heightOfListItem = firstListItem.clientHeight;
const styleTag = document.createElement('style');
document.body.prepend(styleTag);
styleTag.innerHTML = `.list-container{
height: ${heightOfListItem}px;
}`;
};
calculateHeightOfListContainer();
Anzeigen und Ausblenden
Im Moment sieht unsere Liste etwas trist aus – genau wie im ersten Beispiel, nur ohne die Logik zum Hinzufügen oder Entfernen und mit einer völlig anderen Gestaltung als die Liste, die aus <ul>- und <li>-Tags aufgebaut war, die im einführenden Beispiel verwendet wurden.

Wir werden nun etwas tun, das im Moment unerklärlich erscheinen mag, und unsere .list-container- und .list-item-Klassen modifizieren. Wir erstellen auch zusätzliche Stile für beide dieser Klassen, die ihnen nur hinzugefügt werden, wenn eine neue Klasse, .show, in Verbindung mit beiden dieser Klassen separat verwendet wird.
Der Zweck dahinter ist, zwei Zustände sowohl für die .list-container- als auch für die .list-item-Elemente zu schaffen. Ein Zustand ist ohne die .show-Klassen auf beiden dieser Elemente, und dieser Zustand stellt die Elemente dar, während sie aus der Liste heraus animiert werden. Der andere Zustand enthält die .show-Klasse, die beiden dieser Elemente hinzugefügt wurde. Er stellt das angegebene .list-item dar, wie es fest instanziiert und in der Liste sichtbar ist.
In Kürze werden wir zwischen diesen beiden Zuständen wechseln, indem wir die .show-Klasse sowohl vom Elternelement als auch vom Container eines bestimmten .list-item entfernen/hinzufügen. Dies werden wir mit einer CSS-transition zwischen diesen beiden Zuständen kombinieren.
Beachten Sie, dass die Kombination der .list-item-Klasse mit der .show-Klasse zusätzliche Stile hinzufügt. Insbesondere führen wir die Animation ein, bei der sich das Listenelement nach unten ausblendet und sichtbar wird, wenn es zur Liste hinzugefügt wird – das Gegenteil geschieht, wenn es entfernt wird. Da die performanteste Art, Elementpositionen zu animieren, die transform-Eigenschaft ist, werden wir diese hier verwenden und dabei opacity anwenden, um den Sichtbarkeitsteil zu behandeln. Da wir bereits eine transition-Eigenschaft sowohl für die .list-item- als auch für die .list-container-Elemente angewendet haben, findet eine Übergang automatisch statt, wenn wir die .show-Klasse zu beiden Elementen hinzufügen oder entfernen, aufgrund der zusätzlichen Eigenschaften, die die .show-Klasse mitbringt, was einen Übergang auslöst, wann immer wir diese neuen Eigenschaften hinzufügen oder entfernen.
.list-container {
cursor: pointer;
font-size: 3.5rem;
height: 0;
list-style: none;
position: relative;
text-align: center;
width: 300px;
}
.list-container.show:not(:first-child) {
margin-top: 10px;
}
.list-container .list-item {
background-color: #D3D3D3;
left: 0;
opacity: 0;
padding: 2rem 0;
position: absolute;
top: 0;
transform: translateY(-300px);
transition: all 0.6s ease-out;
width: 100%;
}
.list-container .list-item.show {
opacity: 1;
transform: translateY(0);
}
Als Reaktion auf die .show-Klasse gehen wir zurück zu unserer JavaScript-Datei und ändern unsere einzige Funktion so, dass das .list-container-Element nur dann eine height-Eigenschaft erhält, *wenn* das betreffende Element auch eine .show-Klasse hat. Außerdem wenden wir eine transition-Eigenschaft auf unsere standardmäßigen .list-container-Elemente an, und zwar in einer setTimeout-Funktion. Wenn wir das nicht tun würden, würden unsere Container beim anfänglichen Seitenaufruf animiert, wenn das Skript geladen wird und die Höhen zum ersten Mal angewendet werden, was wir nicht wollen.
const listItems = document.querySelectorAll('.list-item');
function calculateHeightOfListContainer(){
const firstListItem = listItems[0];
let heightOfListItem = firstListItem.clientHeight;
const styleTag = document.createElement('style');
document.body.prepend(styleTag);
styleTag.innerHTML = `.list-container.show {
height: ${heightOfListItem}px;
}`;
setTimeout(function() {
styleTag.innerHTML += `.list-container {
transition: all 0.6s ease-out;
}`;
}, 0);
};
calculateHeightOfListContainer();
Wenn wir nun die Markierung in DevTools betrachten, sollten wir sehen können, dass die Liste verschwunden ist und nur noch der Button übrig ist. Die Liste ist nicht verschwunden, weil diese Elemente aus dem DOM entfernt wurden; sie ist verschwunden wegen der .show-Klasse, die nun eine erforderliche Klasse ist, die sowohl dem .list-item- als auch dem .list-container-Element hinzugefügt werden muss, damit wir sie sehen können.
Der Weg, die Liste zurückzubekommen, ist sehr einfach. Wir fügen die .show-Klasse zu all unseren .list-container-Elementen sowie den darin enthaltenen .list-item-Elementen hinzu. Sobald dies geschehen ist, sollten wir unsere vordefinierten Listenelemente wieder an ihrem üblichen Platz sehen können.
<ul class="list">
<li class="list-container show">
<div class="list-item show">List Item</div>
</li>
<li class="list-container show">
<div class="list-item show">List Item</div>
</li>
<li class="list-container show">
<div class="list-item show">List Item</div>
</li>
<li class="list-container show">
<div class="list-item show">List Item</div>
</li>
</ul>
<button class="add-btn">Add New Item</button>
Wir werden jedoch noch nichts mit ihnen interagieren können, denn dafür müssen wir mehr zu unserer JavaScript-Datei hinzufügen.
Das erste, was wir nach unserer anfänglichen Funktion tun werden, ist, Referenzen sowohl auf den Button, den wir klicken, um ein neues Listenelement hinzuzufügen, als auch auf das .list-Element selbst zu deklarieren. Dies ist das Element, das jedes einzelne .list-item und seinen Container umschließt. Dann wählen wir jedes einzelne .list-container-Element aus, das in dem übergeordneten .list-Element verschachtelt ist, und durchlaufen sie alle mit der forEach-Methode. Wir weisen eine Methode in diesem Callback, removeListItem, dem onclick-Event-Handler jedes .list-container zu. Am Ende der Schleife ruft jeder .list-container, der beim Laden einer neuen Seite in das DOM instanziiert wird, dieselbe Methode auf, wenn er geklickt wird.
Sobald dies geschehen ist, weisen wir eine Methode dem onclick-Event-Handler für addBtn zu, damit wir Code aktivieren können, wenn wir darauf klicken. Aber offensichtlich werden wir diesen Code noch nicht erstellen. Vorerst protokollieren wir nur etwas in die Konsole zum Testen.
const addBtn = document.querySelector('.add-btn');
const list = document.querySelector('.list');
function removeListItem(e){
console.log('Deleted!');
}
// DOCUMENT LOAD
document.querySelectorAll('.list .list-container').forEach(function(container) {
container.onclick = removeListItem;
});
addBtn.onclick = function(e){
console.log('Add Btn');
}
Beginnend mit der Arbeit am onclick-Event-Handler für addBtn, wollen wir als Erstes zwei neue Elemente erstellen: container und listItem. Beide Elemente repräsentieren das .list-item-Element und ihr jeweiliges .list-container-Element, weshalb wir ihnen sofort diese genauen Klassen zuweisen, sobald wir sie erstellen.
Sobald diese beiden Elemente vorbereitet sind, verwenden wir die append-Methode auf dem container, um den listItem als Kind darin einzufügen, genau wie diese Elemente, die bereits in der Liste sind, formatiert sind. Nachdem der listItem erfolgreich als Kind in den container eingefügt wurde, können wir das container-Element zusammen mit seinem Kindelement listItem mit der insertBefore-Methode in das DOM verschieben. Das tun wir, weil wir möchten, dass neue Elemente unten in der Liste erscheinen, aber *vor* dem addBtn, der ganz unten in der Liste bleiben muss. Indem wir also das parentNode-Attribut von addBtn verwenden, um sein Elternelement, list, anzusprechen, sagen wir, dass wir das Element als Kind von list einfügen wollen, und das Element, das wir einfügen (container), wird vor dem Kind eingefügt, das bereits im DOM vorhanden ist und das wir mit dem zweiten Argument der insertBefore-Methode, addBtn, ansprechen.
Schließlich, nachdem das .list-item und sein Container erfolgreich zum DOM hinzugefügt wurden, können wir den onclick-Event-Handler des Containers so einstellen, dass er der gleichen Methode entspricht wie jedes andere .list-item, das bereits standardmäßig im DOM vorhanden ist.
addBtn.onclick = function(e){
const container = document.createElement('li');
container.classList.add('list-container');
const listItem = document.createElement('div');
listItem.classList.add('list-item');
listItem.innerHTML = 'List Item';
container.append(listItem);
addBtn.parentNode.insertBefore(container, addBtn);
container.onclick = removeListItem;
}
Wenn wir das ausprobieren, werden wir keine Änderungen an unserer Liste sehen, egal wie oft wir auf addBtn klicken. Dies ist kein Fehler im click-Event-Handler. Die Dinge funktionieren genau so, wie sie sollten. Die .list-item-Elemente (und ihre Container) werden an der richtigen Stelle zur Liste hinzugefügt, es ist nur, dass sie ohne die .show-Klasse hinzugefügt werden. Infolgedessen haben sie keine Höhe, weshalb wir sie nicht sehen und es so aussieht, als würde nichts mit der Liste passieren.
Um jedes neu hinzugefügte .list-item animiert in die Liste einzufügen, wenn wir auf addBtn klicken, müssen wir die .show-Klasse sowohl auf das .list-item als auch auf seinen Container anwenden, genau wie wir es tun mussten, um die bereits hartkodierten Listenelemente im DOM anzuzeigen.
Das Problem ist, dass wir die .show-Klasse nicht sofort auf diese Elemente anwenden können. Wenn wir das tun würden, würde das neue .list-item statisch am Ende der Liste erscheinen, ohne Animation. Wir müssen einige Stile registrieren, bevor die Animation stattfindet, zusätzliche Stile, die diese anfänglichen Stile überschreiben, damit ein Element weiß, welche transition es machen soll. Das bedeutet, wenn wir einfach die .show-Klasse anwenden, sind die Elemente bereits vorhanden – also keine Übergang.
Die Lösung besteht darin, die .show-Klassen in einem setTimeout-Callback anzuwenden und die Aktivierung des Callbacks um 15 Millisekunden oder 1,5/100 Sekunde zu verzögern. Diese unmerkliche Verzögerung ist lang genug, um einen transition vom Ausgangszustand zum neuen Zustand zu erzeugen, der durch das Hinzufügen der .show-Klasse entsteht. Aber diese Verzögerung ist auch kurz genug, dass wir nie wissen werden, dass es überhaupt eine Verzögerung gab.
addBtn.onclick = function(e){
const container = document.createElement('li');
container.classList.add('list-container');
const listItem = document.createElement('div');
listItem.classList.add('list-item');
listItem.innerHTML = 'List Item';
container.append(listItem);
addBtn.parentNode.insertBefore(container, addBtn);
container.onclick = removeListItem;
setTimeout(function(){
container.classList.add('show');
listItem.classList.add('show');
}, 15);
}
Erfolg! Nun ist es an der Zeit, zu behandeln, wie wir Listenelemente entfernen, wenn sie angeklickt werden.
Das Entfernen von Listenelementen sollte jetzt nicht allzu schwierig sein, da wir die schwierige Aufgabe des Hinzufügens bereits hinter uns haben. Zuerst müssen wir sicherstellen, dass das Element, mit dem wir arbeiten, das .list-container-Element und nicht das .list-item-Element ist. Aufgrund der Ereignisweiterleitung ist es wahrscheinlich, dass das Ziel, das diesen Klick ausgelöst hat, das .list-item-Element war.
Da wir mit dem zugehörigen .list-container-Element und nicht mit dem tatsächlichen .list-item-Element arbeiten möchten, das das Ereignis ausgelöst hat, verwenden wir eine while-Schleife, um eine Ebene nach oben zu iterieren, bis das in container gehaltene Element das .list-container-Element ist. Wir wissen, dass es funktioniert, wenn container die Klasse .list-container erhält, was wir durch die Verwendung der contains-Methode auf der classList-Eigenschaft des container-Elements herausfinden können.
Sobald wir Zugriff auf den container haben, entfernen wir sofort die .show-Klasse sowohl vom container als auch von seinem .list-item, sobald wir auch darauf Zugriff haben.
function removeListItem(e) {
let container = e.target;
while (!container.classList.contains('list-container')) {
container = container.parentElement;
}
container.classList.remove('show');
const listItem = container.querySelector('.list-item');
listItem.classList.remove('show');
}
Und hier ist das fertige Ergebnis
Barrierefreiheit & Leistung
Nun mögen Sie versucht sein, das Projekt hier zu belassen, da sowohl das Hinzufügen als auch das Entfernen von Listenelementen nun funktionieren sollte. Aber es ist wichtig zu bedenken, dass diese Funktionalität nur oberflächlich ist und definitiv einige Nachbesserungen vorgenommen werden müssen, um dies zu einem vollständigen Paket zu machen.
Nur weil die entfernten Elemente nach oben und aus dem Blickfeld ausgeblendet sind und die Liste sich zusammengezogen hat, um die hinterlassene Lücke zu füllen, heißt das nicht, dass das entfernte Element aus dem DOM entfernt wurde. Tatsächlich wurde es das nicht. Das ist eine Leistungsschwäche, denn es bedeutet, dass wir Elemente im DOM haben, die keinen anderen Zweck erfüllen, als sich im Hintergrund anzusammeln und unsere Anwendung zu verlangsamen.
Um dies zu lösen, verwenden wir die ontransitionend-Methode auf dem Container-Element, um es aus dem DOM zu entfernen, aber nur, wenn der Übergang, der durch das Entfernen der .show-Klasse verursacht wurde, beendet ist, so dass seine Entfernung unseren Übergang nicht unterbrechen kann.
function removeListItem(e) {
let container = e.target;
while (!container.classList.contains('list-container')) {
container = container.parentElement;
}
container.classList.remove('show');
const listItem = container.querySelector('.list-item');
listItem.classList.remove('show');
container.ontransitionend = function(){
container.remove();
}
}
Wir sollten zu diesem Zeitpunkt keinen Unterschied sehen können, da wir nur die Leistung verbessert haben – keine Stiländerungen.
Der andere Unterschied ist ebenfalls unbemerkbar, aber super wichtig: Kompatibilität. Da wir die korrekten <ul>- und <li>-Tags verwendet haben, sollten Geräte keine Probleme haben, das, was wir erstellt haben, korrekt als unsortierte Liste zu interpretieren.
Weitere Überlegungen zu dieser Technik
Ein Problem, das wir jedoch haben, ist, dass Geräte möglicherweise Probleme mit der dynamischen Natur unserer Liste haben, z. B. wie sich die Liste in ihrer Größe ändert und wie viele Elemente sie enthält. Ein neues Listenelement wird komplett ignoriert und entfernte Listenelemente werden so gelesen, als ob sie noch vorhanden wären.
Um Geräte dazu zu bringen, unsere Liste neu zu interpretieren, wenn sich ihre Größe ändert, müssen wir ARIA-Attribute verwenden. Sie helfen dabei, unsere nicht standardmäßige HTML-Liste von Kompatibilitätsgeräten als solche erkennen zu lassen. Sie sind jedoch hier keine garantierte Lösung, da sie für die Kompatibilität nie so gut sind wie ein natives Tag. Nehmen Sie das <ul>-Tag als Beispiel – Sie müssen sich keine Sorgen machen, da wir das native Element für unsortierte Listen verwenden konnten.
Wir können das Attribut aria-live auf das .list-Element anwenden. Alles, was in einem Abschnitt des DOM markiert ist mit aria-live, wird reaktiv. Mit anderen Worten, Änderungen an einem Element mit aria-live werden erkannt, was ihnen erlaubt, eine aktualisierte Antwort zu geben. In unserem Fall wollen wir, dass die Dinge hochgradig reaktiv sind, und das tun wir, indem wir das aria live-Attribut auf assertive setzen. Auf diese Weise wird es jedes Mal, wenn eine Änderung erkannt wird, diese erkennen und jede Aufgabe unterbrechen, die es gerade ausführt, um sofort auf die vorgenommene Änderung hinzuweisen.
<ul class="list" role="list" aria-live="assertive">
Die Kollapsanimation
Dies ist eine subtilere Animation, bei der sich die Elemente, anstatt dass Listenelemente nach oben oder unten schweben und dabei die Opazität ändern, einfach nach außen zusammenziehen oder ausdehnen, während sie allmählich ein- oder ausgeblendet werden; währenddessen positioniert sich der Rest der Liste neu, um den stattfindenden Übergang zu kompensieren.
Das Coole an der Liste (und vielleicht eine Entschädigung für die umständliche DOM-Struktur, die wir erstellt haben) wäre die Tatsache, dass wir die Animation sehr einfach ändern können, ohne den Haupteffekt zu beeinträchtigen.
Um diesen Effekt zu erzielen, beginnen wir damit, die overflow auf unserem .list-container zu verstecken. Das tun wir, damit sich der .list-container beim Zusammenziehen in sich selbst zurückzieht, ohne dass das Kindelement .list-item über die Grenzen des Listencontainers hinausfließt, während er schrumpft. Abgesehen davon ist das Einzige, was wir noch tun müssen, die Entfernung der transform-Eigenschaft vom .list-item mit der .show-Klasse, da wir nicht möchten, dass sich das .list-item weiterhin nach oben bewegt.
.list-container {
cursor: pointer;
font-size: 3.5rem;
height: 0;
overflow: hidden;
list-style: none;
position: relative;
text-align: center;
width: 300px;
}
.list-container.show:not(:first-child) {
margin-top: 10px;
}
.list-container .list-item {
background-color: #D3D3D3;
left: 0;
opacity: 0;
padding: 2rem 0;
position: absolute;
top: 0;
transition: all 0.6s ease-out;
width: 100%;
}
.list-container .list-item.show {
opacity: 1;
}
Die Seiten-Slide-Animation
Diese letzte Animationstechnik unterscheidet sich stark von den anderen, da die Animation des container und die Animation des .list-item tatsächlich nicht synchron sind. Das .list-item gleitet nach rechts, wenn es aus der Liste entfernt wird, und gleitet von rechts herein, wenn es zur Liste hinzugefügt wird. Es muss genügend vertikaler Platz in der Liste vorhanden sein, um Platz für ein neues .list-item zu schaffen, bevor es überhaupt beginnt, in die Liste zu animieren, und umgekehrt für das Entfernen.
Was das Styling betrifft, ist es sehr ähnlich wie bei der Slide-Down-Opacity-Animation, nur dass die transition für das .list-item nun auf der x-Achse statt auf der y-Achse liegen sollte.
.list-container {
cursor: pointer;
font-size: 3.5rem;
height: 0;
list-style: none;
position: relative;
text-align: center;
width: 300px;
}
.list-container.show:not(:first-child) {
margin-top: 10px;
}
.list-container .list-item {
background-color: #D3D3D3;
left: 0;
opacity: 0;
padding: 2rem 0;
position: absolute;
top: 0;
transform: translateX(300px);
transition: all 0.6s ease-out;
width: 100%;
}
.list-container .list-item.show {
opacity: 1;
transform: translateX(0);
}
Was den onclick-Event-Handler von addBtn in unserem JavaScript betrifft, verwenden wir eine verschachtelte setTimeout-Methode, um den Beginn der listItem-Animation um 350 Millisekunden zu verzögern, nachdem sein container-Element bereits mit der Animation begonnen hat.
setTimeout(function(){
container.classList.add('show');
setTimeout(function(){
listItem.classList.add('show');
}, 350);
}, 10);
In der Funktion removeListItem entfernen wir zuerst die .show-Klasse des Listenelements, damit es sofort mit der Animation beginnen kann. Das übergeordnete container-Element verliert dann seine .show-Klasse, aber erst 350 Millisekunden, nachdem die anfängliche listItem-Animation bereits begonnen hat. Dann, 600 Millisekunden nachdem das container-Element mit der Animation begonnen hat (oder 950 Millisekunden nach der listItem-Animation), entfernen wir das container-Element aus dem DOM, da bis zu diesem Zeitpunkt beide Übergänge des listItem und des Containers beendet sein sollten.
function removeListItem(e){
let container = e.target;
while(!container.classList.contains('list-container')){
container = container.parentElement;
}
const listItem = container.querySelector('.list-item');
listItem.classList.remove('show');
setTimeout(function(){
container.classList.remove('show');
container.ontransitionend = function(){
container.remove();
}
}, 350);
}
Hier ist das Endergebnis
Das ist ein Abschluss!
Da haben Sie es, drei verschiedene Methoden zur Animation von Elementen, die zu einem Stapel hinzugefügt und daraus entfernt werden. Ich hoffe, dass Sie mit diesen Beispielen nun zuversichtlich sind, in einer Situation zu arbeiten, in der sich die DOM-Struktur als Reaktion auf ein Element, das entweder hinzugefügt oder aus dem DOM entfernt wurde, in eine neue Position einpendelt.
Wie Sie sehen können, gibt es viele bewegliche Teile und Dinge zu beachten. Wir begannen damit, was wir von dieser Art von Bewegung in der realen Welt erwarten, und überlegten, was mit einer Gruppe von Elementen passiert, wenn eines davon aktualisiert wird. Es brauchte ein wenig Ausgleich, um zwischen den Zuständen des Zeigens und Verbergens zu wechseln und welche Elemente sie zu bestimmten Zeiten erhielten, aber wir haben es geschafft. Wir sind sogar so weit gegangen, sicherzustellen, dass unsere Liste sowohl performant als auch zugänglich ist, Dinge, die wir in einem realen Projekt definitiv handhaben müssten.
Wie auch immer, ich wünsche Ihnen alles Gute für Ihre zukünftigen Projekte. Und das ist alles von mir. Ende.
Ich habe den Leitfaden durchgearbeitet, fand ihn aber für einfache Hinzufüge- und Löschanimationen ziemlich kompliziert. Ich habe es versucht, und bin zu diesem Ergebnis gekommen: https://jsbin.com/bopapiriqu/
Es verwendet nur Ränder.
Ich weiß, dass dies nicht annähernd an die obigen Animationen herankommt, aber es erfüllt für mich seinen Zweck.
Ich bin letzten Winter auf dieses Problem gestoßen und habe diese Lösung entwickelt.
Es behandelt nur den Löschfall und verwendet einen etwas anderen Ansatz, Grid für das Layout und was die Bewegung angeht, läuft alles auf Folgendes hinaus: Sobald ein Kachel geschlossen ist (bei
transitionend), ermittle ich seine Position und die Position aller nachfolgenden Kacheln und animiere per CSS alle nachfolgenden Kacheln zur Position der vorherigen Kachel; danach (beianimationend) entferne ich die geschlossene Kachel aus dem DOM.Ein guter Trick ist, die Zeilenhöhe, den Innenabstand und/oder den Außenabstand auf Null zu animieren. Nicht so professionell, aber es gleitet (mehr oder weniger) anstatt zu poltern.
Ich habe versucht, dieses Problem zu lösen, und bin auf diesen Blogbeitrag gestoßen. Bitte verzeihen Sie, aber ich bin enttäuscht beim Lesen, ich werde die folgenden Gründe nennen, „WARUM“ und meine Lösung geben
1. Es erklärt nicht das Wichtigste, was den Übergang reibungslos macht, wenn ein Element entfernt wird, während verbleibende Elemente Platz im DOM einnehmen.
2. Die verwendete Methode ist zu kompliziert, sodass ich Schwierigkeiten habe, den Code zu lesen und zu verstehen, was jeder einzelne Teil mit dem Animationseffekt zu tun hat.
3. Die Technik ist zu schwierig und unordentlich, bei der Programmierung ist Einfachheit am besten, warum muss man die Höhensteuerung in JavaScript programmieren, wenn man das in CSS machen kann?
Ich habe mir die Lösung von duckydude20 angesehen und mag die Einfachheit des Codes. Aber ich habe auch eine eigene Lösung gefunden, die jedoch nur das Element mit Animationseffekt entfernt, da das Hinzufügen von Elementen ziemlich einfach ist.
Die Erklärung werde ich dort einfügen. https://codepen.io/nato11111/pen/GRxYZJZ