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

SVG加载进度边框描边粗细随视口尺寸变化不一致的解决方法咨询

SVG加载进度边框描边粗细随视口尺寸变化不一致的解决方法咨询

我正在做一个用SVG路径实现的加载屏,用灰色路径表示未加载的边框,白色路径表示加载进度。加载边框的逻辑是从右上角(0%)开始,到左下角(25%)、右下角(50%)、右上角(75%),最后回到左上角(100%)完成加载。

目前所有功能都正常,唯独遇到一个关键问题:当视口高度大于宽度(或者反之)时,边框的上下边和左右边描边粗细不一样——要么上下边更粗,要么左右边更粗。我想要不管视口尺寸怎么变,描边粗细都保持均匀,而且还能调整这个粗细来适配移动端。

我已经尝试过这些方法,但都没解决问题:

  • 设置stroke-width="1"
  • 设置stroke-width="1px"
  • 设置stroke-width="1%"
  • 设置stroke-width="1vmin"
  • 使用vector-effect:non-scaling-stroke;但会破坏动画
  • 同时使用vector-effect:non-scaling-stroke;和vector-effect:non-rotation;
  • 用JavaScript动态根据视口设置宽度(代码没保留)

当前代码

HTML

<!-- Loading Screen -->
<div id="loading-screen">
  <div id="loading-content">
    <img id="ws-logo" src="ws-logo.png" alt="WS Logo">
    <div id="loading-percentage">0%</div>
  </div>
  <svg id="loading-bar" width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="none">
          <!-- Grey background path -->
          <path
            d="M5 5 L5 95 L95 95 L95 5 Z"
            stroke="#666"
            stroke-width="2vmin"
            stroke-linecap="square"
            fill="none"
          />
          <!-- White progress path -->
          <path
            id="progress-path"
            d="M5 5 L5 95 L95 95 L95 5 Z"
            stroke="#fff"
            stroke-width="2vmin"
            stroke-linecap="square"
            fill="none"
            stroke-dasharray="0"
            stroke-dashoffset="0"
          />
        </svg>
</div>

CSS

#loading-screen {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background-color: #222;
  /* Dark background */
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 9999;
  /* Overlay all content */
  overflow: hidden;
  transition: opacity 0.5s ease;
  /* Fade-out effect */
}

/* Logo and percentage container */
#loading-content {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  z-index: 1;
  /* Above the loading bar */
}

/* Logo styling */
#ws-logo {
  width: 15%;
  max-width: 100px;
  height: auto;
  margin-bottom: 10px;
}

/* Percentage text styling */
#loading-percentage {
  font-size: 1.5em;
  color: #fff;
  font-family: Arial, sans-serif;
}

/* SVG loading bar */
#loading-bar {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  /* Behind content */
  padding: 0;
  /* Ensure no internal spacing */
  margin: 0;
  /* Remove any external margin */
  box-sizing: border-box;
  /* Include border in dimensions */
}

#loading-bar path {
  vector-effect: non-scaling-stroke;
}

#progress-path {
  transition: stroke-dashoffset 0.2s ease-out;
  /* Smooth animation for the progress bar */
}

JavaScript

document.addEventListener("DOMContentLoaded", () => {
  const loadingScreen = document.getElementById("loading-screen");
  const loadingPercentage = document.getElementById("loading-percentage");
  const progressPath = document.getElementById("progress-path");

  let progress = 0; // Current progress
  const minDisplayTime = 1750; // Minimum display time in milliseconds
  const fadeDelay = 500; // Duration of fade-out animation (in ms)
  const delayAfterComplete = 750; // Delay after hitting 100% (in ms)
  const startTime = Date.now(); // Record when loading starts
  // 动态获取路径实际总长度,替代硬编码的360
  const totalLength = progressPath.getTotalLength();

  // Set the initial stroke-dasharray and stroke-dashoffset for the white bar
  progressPath.style.strokeDasharray = totalLength;
  progressPath.style.strokeDashoffset = totalLength;

  // Function to update progress
  function updateProgress() {
    const elapsedTime = Date.now() - startTime;

    // Calculate the progress as a percentage based on time
    if (elapsedTime < minDisplayTime) {
      progress = Math.min((elapsedTime / minDisplayTime) * 100, 100);
    } else {
      progress = 100; // Ensure progress reaches 100% after minimum display time
    }

    // Update percentage text
    loadingPercentage.textContent = `${Math.floor(progress)}%`;

    // Update progress bar position
    const offset = totalLength * (1 - progress / 100);
    progressPath.style.strokeDashoffset = offset;

    console.log(`Loading progress: ${progress}%`);

    // Trigger fade-out when progress hits 100%
    if (progress === 100) {
      const remainingTime = minDisplayTime - elapsedTime; // Ensure minimum display time
      setTimeout(() => {
        // Add a 0.75-second delay after hitting 100% before starting fade-out
        setTimeout(() => {
          loadingScreen.style.opacity = "0"; // Trigger fade-out animation

          // Completely hide the loading screen after the fade-out
          setTimeout(() => {
            loadingScreen.style.display = "none";
          }, fadeDelay); // Match the CSS transition duration
        }, delayAfterComplete); // 0.75-second delay after hitting 100%
      }, Math.max(remainingTime, 0)); // Wait for any remaining time if necessary
    } else {
      // Continue updating progress until it reaches 100%
      requestAnimationFrame(updateProgress);
    }
  }

  // Start the progress update loop
  updateProgress();
});

解决思路说明

  1. 启用non-scaling-stroke:这个属性会让SVG描边不随SVG的缩放而变化,不管视口是宽屏还是竖屏,描边粗细都保持一致。之前你用这个属性时动画出错,是因为硬编码了路径总长度,导致计算的stroke-dashoffset和实际路径不匹配。
  2. 动态获取路径总长度:用progressPath.getTotalLength()替代硬编码的360,这样不管视口怎么缩放,都能拿到当前路径的真实长度,保证动画计算准确。
  3. 用vmin设置描边粗细:vmin是视口最小边的百分比单位,比如2vmin就是视口宽高中较小值的2%,这样在移动端和桌面端都能保持合适的粗细,而且不会随视口拉伸变形。

这样修改后,你的加载边框描边就能在任何视口尺寸下保持均匀粗细,同时动画也能正常运行,还能通过调整vmin的数值来适配不同设备的需求。

备注:内容来源于stack exchange,提问作者Antonio Higdon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 13:44:53