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

HTML Canvas可拖拽圆形容器滚动条无法实时同步的解决方法

解决Canvas圆形容器滚动条实时同步问题

问题原因

你当前代码中计算圆弧滚动进度的逻辑错误,直接使用container.scrollTop来计算弧度,没有基于滚动比例(已滚动距离/总可滚动距离),导致圆弧无法准确反映实际滚动进度。

修复方案

  1. 利用已计算好的scrollPer(范围01)映射圆弧的弧度(02π)
  2. 调整圆弧起始角度为-Math.PI/2(对应圆形顶部),让滚动条视觉表现更符合用户直觉
  3. 确保每次滚动时,圆弧绘制完全基于当前滚动比例,而非原始scrollTop值

修改后的完整代码

HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>圆形滚动容器</title>
</head>
<body>
  <h1>Hi</h1>
<section class="scrollbar" id="container">
<main>
  <div class="item">
    <p>hola</p>
  </div>
  <div class="item">
    <p>hola</p>
  </div>
  <div class="item">
    <p>hola</p>
  </div>
  <div class="item">
    <p>hola</p>
  </div>
  <div class="item">
    <p>hola</p>
  </div>
</main>
<canvas width="100" height="100" class="canvas" id="canvas"></canvas>
</section>
</body>
</html>

CSS

:root{
  --item-size: 40dvw;
}
*{
  box-sizing: content-box;
}
html {
  scrollbar-width:none;
  -ms-overflow-style:none;
  overflow-y:-moz-scrollbars-none
}
.hide-scrollbar::-webkit-scrollbar {
  width:0;
  display: none
}
body{
  display: flex;
  flex-wrap: wrap;
  flex-direction: column;
  align-content: center;
  justify-content: center;
  align-items: center;
  height: 100vh;
  margin: 0;
  background-color: rgba(185, 212, 235, 0.698);
}
section{
  border-radius: 50%;
  width: calc(var(--item-size) + 5.9px);
  height: calc(var(--item-size) + 5.9px);
  gap: 2px;
  position: relative;
  overflow: hidden;
}
main{
  display: grid;
  grid-auto-flow: row;
  width: 100%;
  height: 100%;
  gap: 2px;
  overflow: scroll;
  scrollbar-width: none;
  scroll-snap-type: y mandatory;
  scroll-behavior: smooth;
  position: relative;
  box-sizing: border-box;
}
main>.item{
  background-color: aliceblue;
  border: 3px dashed black;
  border-radius: 50%;
  aspect-ratio: 1;
  width: 100%;
  box-sizing: border-box;
  display: flex;
  align-content: center;
  justify-content: center;
  scroll-snap-align: start;
}
.canvas{
  position: absolute; 
  right: 0px; top: 0px;
  border-radius: 50%;
  pointer-events: none;
}

JavaScript

const canvas = document.getElementById('canvas');
const container = document.getElementById('container');
const ctx = canvas.getContext('2d');

const resizeCanvas = () => {
    canvas.height = container.clientHeight;
    canvas.width = container.clientHeight;
}

let containerHeight, containerScrollHeight, scrolled, scrollPer;

const render = () => {
    containerHeight = container.offsetHeight;
    containerScrollHeight = container.scrollHeight;
    scrolled = container.scrollTop;
    // 计算滚动比例:0(顶部)到1(底部)
    scrollPer = scrolled / (containerScrollHeight - containerHeight);
    
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.lineWidth = 5;
    // 绘制灰色背景圆弧
    makeArc('#666666', canvas.width / 2, canvas.height / 2, canvas.height / 2.05, 0, 2 * Math.PI);
    // 绘制黑色进度圆弧:从顶部(-π/2)开始,根据滚动比例计算结束角度
    const startAngle = -Math.PI / 2;
    const endAngle = startAngle + scrollPer * 2 * Math.PI;
    makeArc('#000000', canvas.width / 2, canvas.height / 2, canvas.height / 2.05, startAngle, endAngle);
};

const makeArc = (color, x, y, radius, startAngle, endAngle) => {
    ctx.beginPath();
    ctx.strokeStyle = color;
    ctx.arc(x, y, radius, startAngle, endAngle);
    ctx.stroke();
};

resizeCanvas();
render();
window.addEventListener('resize', () => {
    resizeCanvas();
    render();
});
container.addEventListener('scroll', render);

关键修改点

  • 用scrollPer * 2 * Math.PI计算进度圆弧的结束角度,确保滚动比例与圆弧进度完全同步
  • 将圆弧起始角度设为-Math.PI/2,对应圆形顶部,符合用户对滚动条的视觉预期
  • 优化makeArc函数参数命名,提升代码可读性
  • 调整CSS中scroll-behavior为smooth,优化滚动体验

内容的提问来源于stack exchange,提问作者polluelo_js

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 02:07:09