You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何结合边框动画实现图片网格的渐进式显示?

问题说明

我实现了一个先向下再向右扩展的边框动画,用于展示图片网格。但当前通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 08:14:58