用於添加和刪除堆棧項目的動畫技術
CSS動畫,簡單易用,但複雜場景卻極具挑戰。例如,鼠標懸停時改變按鈕背景顏色?簡單。但以高性能的方式動畫化元素的位置和大小,並影響其他元素的位置?棘手!本文將深入探討這個問題。
一個常見例子是從一堆項目中移除一個項目。堆疊在頂部的項目需要向下移動以彌補從堆棧底部移除的項目的空隙。現實生活中就是這樣運作的,用戶可能期望網站上出現這種逼真的運動。如果沒有,用戶可能會感到困惑或暫時迷失方向。基於生活經驗,你期望某些事物以某種方式運作,卻得到完全不同的結果,用戶可能需要額外的時間來處理這種不切實際的運動。
這裡演示了一個添加項目(點擊按鈕)或移除項目(點擊項目)的UI。
你可以通過添加“淡出”動畫等方式略微掩蓋糟糕的UI,但結果不會很好,因為列表會突然折疊並導致同樣的認知問題。
將僅CSS動畫應用於動態DOM事件(添加全新的元素和完全移除元素)是極其棘手的工作。我們將直接面對這個問題,並介紹三種截然不同的動畫類型來處理這個問題,所有這些都實現了幫助用戶理解項目列表變化的目標。完成本文後,你將能夠使用這些動畫,或者根據這些概念構建你自己的動畫。
我們還將簡要介紹可訪問性以及如何借助ARIA屬性使精細的HTML佈局仍然保留與輔助設備的一些兼容性。
滑動淡出動畫
一種非常現代的方法(也是我個人最喜歡的)是,新添加的元素根據它們最終將要到達的位置垂直淡入淡出。這也意味著列表需要“打開”一個位置(也進行了動畫處理)來為其騰出空間。如果一個元素離開了列表,它佔據的空間需要收縮。
由於我們同時進行許多不同的操作,我們需要更改DOM結構以將每個.list-item
包裝到一個恰當命名的.list-container
容器類中。這對於使我們的動畫工作絕對至關重要。
- List Item
- List Item
- List Item
- List Item
現在,這種樣式是不正統的,因為為了使我們的動畫效果稍後能夠工作,我們需要以非常特定的方式設置列表樣式,這以犧牲一些慣用的CSS實踐為代價來完成任務。
.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; }
如何處理間距
首先,我們使用margin-top
在堆棧中的元素之間創建垂直空間。底部沒有邊距,以便其他列表項可以填充移除的列表項創建的間隙。這樣,即使我們將容器高度設置為零,它仍然在底部有邊距。額外的空間是在刪除的列表項正下方的列表項之間創建的。並且同一個列表項應該向上移動以響應刪除的列表項的容器高度為零。並且由於這個額外的空間進一步擴大了列表項之間的垂直間隙,因此我們使用margin-top
來防止這種情況發生。
但是我們只有在所討論的項目容器不是列表中的第一個項目時才這樣做。這就是我們使用:not(:first-child)
的原因——它定位所有除了第一個容器(啟用選擇器)。我們這樣做是因為我們不希望第一個列表項從列表的頂部邊緣向下推。我們只想讓此操作發生在之後的所有項目上,因為它們位於另一個列表項的正下方,而第一個列表項則不是。
現在,這不太可能完全有意義,因為我們目前沒有將任何元素的高度設置為零。但我們稍後會這樣做,為了使列表元素之間的垂直間距正確,我們需要像我們那樣設置邊距。
關於定位的說明
另一個值得指出的內容是.list-item
元素嵌套在其父.list-container
元素中的事實,它們被設置為具有absolute
位置,這意味著它們相對於其相對定位的.list-container
元素在DOM之外定位。我們這樣做是為了使.list-item
元素在移除時向上浮動,同時使其他.list-item
元素移動並填充移除此.list-item
元素留下的間隙。發生這種情況時, .list-container
元素(未絕對定位,因此受DOM影響)會折疊其高度,允許其他.list-container
元素填充其位置,而.list-item
元素(以絕對方式定位)向上浮動,但不影響列表的結構,因為它不受DOM的影響。
處理高度
不幸的是,我們還沒有做足夠的努力來獲得一個合適的列表,其中各個列表項一個接一個地堆疊在一起。相反,目前我們所能看到的只是一個.list-item
,它代表所有堆疊在同一位置的列表項。這是因為,儘管.list-item
元素可能通過其padding
屬性具有一定的高度,但它們的父元素沒有,而是高度為零。這意味著我們在DOM中沒有任何東西實際上將這些元素彼此分開,因為要做到這一點,我們需要我們的.list-container
元素具有一定的高度,因為與它們的子元素不同,它們受DOM的影響。
為了使我們的列表容器的高度與它們的子元素的高度完全匹配,我們需要使用JavaScript。因此,我們將所有列表項存儲在一個變量中。然後,我們創建一個函數,該函數在腳本加載後立即調用。
這成為處理列表容器元素高度的函數:
const listItems = document.querySelectorAll('.list-item'); function calculateHeightOfListContainer(){ }; calculateHeightOfListContainer();
我們首先做的就是從列表中提取第一個.list-item
元素。我們可以這樣做,因為它們的大小都相同,因此我們使用哪個元素無關緊要。一旦我們訪問了它,我們就通過元素的clientHeight
屬性以像素為單位存儲其高度。之後,我們創建一個新的