如何结合边框动画实现图片网格的渐进式显示?
问题说明
我实现了一个先向下再向右扩展的边框动画,用于展示图片网格。但当前通过addEventListener("click")将display设置为grid时,内容会立即显示。希望实现以下两种效果之一:
- 等待边框动画完成后,再让
display:"grid"的内容淡入显示; - 随着边框向右扩展,逐步显示预加载的图片。
方案1:等待边框动画完成后淡入内容
核心逻辑:利用animationend事件监听边框动画结束,再触发内容的淡入过渡,同时通过CSS控制内容初始状态为隐藏。
修改后的完整代码
HTML
<fieldset id="console-select"> <legend>Radio Option</legend> <button type="button" id="option-button">Select</button> <div id="option-grid"> <div class="option-radio hide"> <label for="1"> <input type="radio" name="option" value="1" id="1" hidden> <img src="https://placehold.co/600x200" id="option-1-image"> </label> </div> <div class="option-radio hide"> <label for="2"> <input type="radio" name="option" value="2" id="2" hidden> <img src="https://placehold.co/600x199" id="option-2-image"> </label> </div> <div class="option-radio hide"> <label for="3"> <input type="radio" name="option" value="3" id="3" hidden> <img src="https://placehold.co/600x198" id="option-3-image"> </label> </div> <div class="option-radio hide"> <label for="4"> <input type="radio" name="option" value="4" id="4" hidden> <img src="https://placehold.co/600x197" id="option-4-image"> </label> </div> </div> </fieldset>
CSS
.hide { opacity: 0; pointer-events: none; /* 隐藏时禁止点击交互 */ transition: opacity 0.3s ease; } .active { opacity: 1; pointer-events: auto; } #option-button { width: 600px; height: 200px; } #option-grid { display: none; grid-template-columns: repeat(2, 1fr); width: 1250px; height: 450px; place-items: center; border: 5px solid black; border-radius: 10px; overflow: hidden; /* 防止内容溢出动画中的小容器 */ } @keyframes open-animation { 0% { height: 10px; width: 10px; } 50% { height: 450px; width: 10px; } 100% { width: 1250px; } }
JS
const showButton = document.getElementById("option-button"); const optionsGrid = document.getElementById("option-grid"); const optionRadios = document.getElementsByClassName("option-radio"); const optionOne = document.getElementById("1"); const optionTwo = document.getElementById("2"); const optionThree = document.getElementById("3"); const optionFour = document.getElementById("4"); const optionOneImage = document.getElementById("option-1-image").src; const optionTwoImage = document.getElementById("option-2-image").src; const optionThreeImage = document.getElementById("option-3-image").src; const optionFourImage = document.getElementById("option-4-image").src; // 监听边框动画结束事件 optionsGrid.addEventListener('animationend', () => { // 动画完成后统一显示内容 for (let i = 0; i < optionRadios.length; i++) { optionRadios[i].classList.replace("hide", "active"); } }); showButton.addEventListener("click", showOptions); function showOptions() { const isHidden = Array.from(optionRadios).some(el => el.classList.contains('hide')); if (isHidden) { optionsGrid.style.display = "grid"; // 重置动画(避免重复点击时动画不触发) optionsGrid.style.animation = 'none'; setTimeout(() => { optionsGrid.style.animation = "open-animation 500ms linear 1"; }, 10); // 先保持内容隐藏状态 for (let i = 0; i < optionRadios.length; i++) { optionRadios[i].classList.add("hide"); } } else { optionsGrid.style.display = "none"; for (let i = 0; i < optionRadios.length; i++) { optionRadios[i].classList.replace("active", "hide"); } } } // 选项点击逻辑 optionOne.addEventListener("click", function() { showButton.style.backgroundImage = `url("${optionOneImage}")`; showButton.innerHTML = ""; optionsGrid.style.display = "none"; for (let i = 0; i < optionRadios.length; i++) { optionRadios[i].classList.replace("active", "hide"); } }); optionTwo.addEventListener("click", function() { showButton.style.backgroundImage = `url("${optionTwoImage}")`; showButton.innerHTML = ""; optionsGrid.style.display = "none"; for (let i = 0; i < optionRadios.length; i++) { optionRadios[i].classList.replace("active", "hide"); } }); optionThree.addEventListener("click", function() { showButton.style.backgroundImage = `url("${optionThreeImage}")`; showButton.innerHTML = ""; optionsGrid.style.display = "none"; for (let i = 0; i < optionRadios.length; i++) { optionRadios[i].classList.replace("active", "hide"); } }); optionFour.addEventListener("click", function() { showButton.style.backgroundImage = `url("${optionFourImage}")`; showButton.innerHTML = ""; optionsGrid.style.display = "none"; for (let i = 0; i < optionRadios.length; i++) { optionRadios[i].classList.replace("active", "hide"); } });
方案2:随边框扩展逐步显示图片
核心逻辑:通过requestAnimationFrame实时跟踪网格容器的宽度变化,根据宽度进度依次显示对应位置的图片,模拟随边框扩展逐步展示的效果。
关键改动部分
CSS(仅修改状态类)
.hide { opacity: 0; pointer-events: none; transition: opacity 0.2s ease; /* 加快淡入速度,匹配边框动画节奏 */ } .active { opacity: 1; pointer-events: auto; } /* 其他CSS保持不变 */
JS(修改showOptions函数)
showButton.addEventListener("click", showOptions); function showOptions() { const isHidden = Array.from(optionRadios).some(el => el.classList.contains('hide')); if (isHidden) { optionsGrid.style.display = "grid"; // 重置动画 optionsGrid.style.animation = 'none'; setTimeout(() => { optionsGrid.style.animation = "open-animation 500ms linear 1"; }, 10); // 先隐藏所有内容 for (let i = 0; i < optionRadios.length; i++) { optionRadios[i].classList.add("hide"); } // 跟踪宽度变化,按进度显示图片 const trackWidth = () => { const currentWidth = optionsGrid.offsetWidth; const totalWidth = 1250; const progress = currentWidth / totalWidth; // 按左列→右列的顺序逐步显示 if (progress >= 0.25) optionRadios[0].classList.replace('hide', 'active'); if (progress >= 0.5) optionRadios[2].classList.replace('hide', 'active'); if (progress >= 0.75) optionRadios[1].classList.replace('hide', 'active'); if (progress >= 1) { optionRadios[3].classList.replace('hide', 'active'); cancelAnimationFrame(trackId); } else { trackId = requestAnimationFrame(trackWidth); } }; let trackId = requestAnimationFrame(trackWidth); } else { optionsGrid.style.display = "none"; for (let i = 0; i < optionRadios.length; i++) { optionRadios[i].classList.replace("active", "hide"); } } } // 其他JS逻辑保持不变
内容的提问来源于stack exchange,提问作者PsychedelicSeal
相关产品推荐
相关产品推荐

