创建圆角曲线:CSS、Canvas与贝塞尔曲线实战指南

发布时间:2026/8/26 6:03:04
创建圆角曲线:CSS、Canvas与贝塞尔曲线实战指南 项目标题 BC13-7-2 创建圆角曲线 看起来像是一节课的目录编号但内容非常实用。圆角在界面设计、数据可视化、游戏绘制里处处可见。本文围绕 CSS 圆角、Canvas 圆角矩形、贝塞尔曲线三类核心实现展开从基础语法到完整案例逐步拆解适合前端初学者、Canvas 图形爱好者以及需要自定义图表样式的开发者收藏学习。1. 圆角曲线到底是什么1.1 圆角曲线的应用场景圆角曲线简单来说就是把原本生硬的直角或直线连接处用一段平滑的圆弧替代。生活中的很多产品都大量使用了圆角设计手机 App 的卡片、按钮、弹窗、头像框、进度条、图表标签等。圆角带来的视觉感受更柔和、亲和也能减少图形之间的尖锐冲突。在 Web 开发中创建圆角曲线主要有三条路线纯 CSS通过border-radius属性给元素添加圆角适合按钮、卡片、输入框等普通 UI 元素。Canvas 2D API通过roundRect、arcTo、arc等方法绘制圆角矩形、圆弧、圆角进度环等适合图形绘制、游戏开发、数据可视化。贝塞尔曲线通过quadraticCurveTo、bezierCurveTo绘制任意曲线路径适合制作波浪线、折线图平滑过渡、不规则图形。本文会围绕这三类最常用的实现方式展开并用一个综合案例把 CSS 圆角和 Canvas 圆角曲线结合起来形成一套可以直接落地的方案。1.2 为什么需要单独理解“曲线”而不是“圆角矩形”很多初学者以为圆角曲线就是border-radius: 20px这么简单。但在 Canvas 或 SVG 这类绘图环境中浏览器的底层并不认识“圆角矩形”这个概念它只知道点、线、弧、贝塞尔曲线。你要告诉它“从哪个点开始沿哪个角度画一段弧再连接到哪个点”它才能绘制出圆角效果。这就是“创建圆角曲线”的核心价值理解底层绘制原理才能在遇到浏览器兼容性差异、需要自定义圆角大小、需要绘制非矩形圆角形状时灵活应对。2. 环境准备与基础工程搭建2.1 运行环境本文的示例全部基于浏览器运行不需要安装任何额外依赖。你只需要一个现代浏览器Chrome、Edge、Firefox、Safari 均可。一个代码编辑器VS Code、WebStorm、记事本都行。基础的 HTML、CSS、JavaScript 语法认知。版本方面不需要刻意追求最新。CanvasRenderingContext2D.roundRect()是较新的 API在 Chrome 99、Edge 99、Firefox 112、Safari 16 中已经原生支持。如果你的项目需要兼容旧浏览器我会在后面的章节给出arcTo兼容方案所以即使环境不支持roundRect也能正常运行。2.2 示例项目结构为了方便阅读我建议按下面的目录结构组织文件round-curve-demo/ ├── index.html // 主页面 ├── css/ │ └── style.css // 样式文件 └── js/ └── main.js // Canvas 绘制逻辑如果你只是为了快速验证也可以把所有代码写到一个 HTML 文件中。本文为了让结构更清晰会拆分成三个文件。实际开发中这种“结构、样式、脚本”分离的方式更利于维护。3. 核心原理与基础实现3.1 CSS border-radius 创建圆角border-radius是圆角曲线中最容易理解的一个属性。它的语法可以接收 1 到 4 个值分别对应左上、右上、右下、左下四个角的圆角半径。我们来写一个最简单的例子。先创建index.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title创建圆角曲线/title link relstylesheet hrefcss/style.css /head body div classcard h2圆角卡片/h2 p这是通过 border-radius 创建的圆角效果。/p /div /body /html然后在css/style.css中定义样式body { font-family: Microsoft YaHei, PingFang SC, sans-serif; background: #f0f2f5; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; } .card { width: 320px; padding: 24px; background: #ffffff; border-radius: 16px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); } .card h2 { margin: 0 0 12px 0; font-size: 20px; color: #333333; } .card p { margin: 0; font-size: 14px; color: #666666; line-height: 1.6; }这里最核心的一行是border-radius: 16px。它让卡片的四个角都变成了半径为 16 像素的圆角。如果你希望四个角不一样可以写四个值border-radius: 8px 16px 24px 32px;顺序是左上角、右上角、右下角、左下角按顺时针排列。border-radius还支持椭圆角写法是用斜杠分隔两个半径border-radius: 30px / 50px;这种写法表示水平半径为 30 像素垂直半径为 50 像素。在一些特殊设计稿里会用到比如胶囊按钮、椭圆标签等。日常开发中border-radius是性价比最高的圆角方案因为它不需要任何 JavaScript 逻辑浏览器自动完成绘制。3.2 Canvas roundRect 绘制圆角矩形Canvas 里的圆角曲线比 CSS 要底层一些。我们先来看最直接的方式ctx.roundRect()。roundRect(x, y, width, height, radii)方法有五个核心参数x和y矩形左上角的坐标。width和height矩形宽高。radii圆角半径可以是一个数字也可以是一个数组。数组形式可以精确控制每个角的半径顺序是[左上, 右上, 右下, 左下]。下面这段代码在 Canvas 上绘制两个圆角矩形// 文件路径js/main.js const canvas document.getElementById(curveCanvas); const ctx canvas.getContext(2d); // 绘制第一个圆角矩形边框 ctx.beginPath(); ctx.roundRect(20, 20, 200, 100, 16); ctx.strokeStyle #4A90D9; ctx.lineWidth 3; ctx.stroke(); // 绘制第二个圆角矩形填充 ctx.beginPath(); ctx.roundRect(240, 20, 200, 100, [8, 24, 8, 24]); ctx.fillStyle #F5A623; ctx.fill();运行后第一个矩形四个角半径都是 16 像素带蓝色描边第二个矩形四个角半径分别为 8、24、8、24 像素是橙色填充。注意调用roundRect后如果只想画边框就调用stroke()只想填充就调用fill()两者想要同时显示就都调用。roundRect支持传入负半径吗不支持。如果传入负数会抛出异常或者被当作 0 处理。实际项目中建议先对半径做一层校验保证传入值 0。3.3 arcTo 兼容方案虽然roundRect很方便但旧浏览器不支持。更常见的做法是用arcTo()方法手动绘制圆角矩形。arcTo(x1, y1, x2, y2, radius)的作用是从当前路径点开始向点(x1, y1)画一条直线然后在接近点(x2, y2)的位置用指定半径的圆弧做平滑过渡。我们可以封装一个通用的drawRoundRect函数function drawRoundRect(ctx, x, y, width, height, radius) { radius Math.min(radius, width / 2, height / 2); ctx.beginPath(); ctx.moveTo(x radius, y); ctx.lineTo(x width - radius, y); ctx.arcTo(x width, y, x width, y radius, radius); ctx.lineTo(x width, y height - radius); ctx.arcTo(x width, y height, x width - radius, y height, radius); ctx.lineTo(x radius, y height); ctx.arcTo(x, y height, x, y height - radius, radius); ctx.lineTo(x, y radius); ctx.arcTo(x, y, x radius, y, radius); ctx.closePath(); }这个函数的思路是从左上角附近开始先画上边直线再用arcTo画右上角的圆弧再画右边直线再画右下角圆弧依此类推把四个角都替换成圆弧。需要注意的是半径不能超过宽高的一半否则圆弧会异常。所以在函数开头做了限制radius Math.min(radius, width / 2, height / 2);这样即使外部传入很大的半径也不会绘制出奇怪的结果。使用方式和roundRect类似const canvas document.getElementById(curveCanvas); const ctx canvas.getContext(2d); drawRoundRect(ctx, 20, 150, 200, 100, 20); ctx.fillStyle #52C41A; ctx.fill();3.4 二次贝塞尔曲线 quadraticCurveTo圆角矩形适合标准卡片但如果想画波浪线、圆角折线、弧形标签就需要用到贝塞尔曲线。贝塞尔曲线是计算机图形学中描述任意曲线路径的基础工具。quadraticCurveTo(cpx, cpy, x, y)是二次贝塞尔曲线它需要三个点起点由moveTo或上次路径的终点决定。控制点(cpx, cpy)决定曲线弯曲方向和程度。终点(x, y)。控制点并不在最终曲线上它是“拉拽”曲线形状的虚拟点。控制点离起点和终点越远曲线弯曲得越明显。来看一个最经典的例子const canvas document.getElementById(curveCanvas); const ctx canvas.getContext(2d); ctx.beginPath(); ctx.moveTo(20, 200); ctx.quadraticCurveTo(100, 80, 180, 200); ctx.lineWidth 3; ctx.strokeStyle #722ED1; ctx.stroke();这段代码从(20, 200)出发以(100, 80)为控制点到达(180, 200)。最终你会看到一条类似“抛物线”的曲线中间向上拱起。如果想做对称的波浪可以把两条二次贝塞尔曲线拼接起来ctx.beginPath(); ctx.moveTo(20, 260); ctx.quadraticCurveTo(60, 220, 100, 260); ctx.quadraticCurveTo(140, 300, 180, 260); ctx.strokeStyle #EB2F96; ctx.lineWidth 3; ctx.stroke();这段代码先向上弯曲再向下弯曲形成一个小波浪。3.5 三次贝塞尔曲线 bezierCurveTo二次贝塞尔曲线只有一个控制点适合简单的弧线。如果想要 S 形曲线、更复杂的轮廓需要三次贝塞尔曲线。bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)比二次贝塞尔曲线多一个控制点第一个控制点(cp1x, cp1y)控制曲线起点的切线方向。第二个控制点(cp2x, cp2y)控制曲线终点的切线方向。(x, y)曲线终点。示例ctx.beginPath(); ctx.moveTo(20, 320); ctx.bezierCurveTo(80, 240, 120, 360, 180, 300); ctx.strokeStyle #13C2C2; ctx.lineWidth 3; ctx.stroke();这段代码绘制了一条 S 形曲线。第一个控制点拉高前半段第二个控制点降低后半段从而形成“起伏”效果。在实际项目中三次贝塞尔曲线很适合做折线图的平滑过渡。比如有一组数据点默认折线图是直线连接但用贝塞尔曲线可以让线条变得更柔和。4. 综合实战圆角数据卡片与进度环基础语法掌握之后我们把它们组合成一个完整项目一个带圆角的数据展示卡片卡片内部有一个圆角进度环、一个圆角按钮和一段圆角统计标签。这个案例在后台管理系统中很常见。4.1 创建页面结构先更新index.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleBC13-7-2 创建圆角曲线实战/title link relstylesheet hrefcss/style.css /head body div classdata-card div classcard-header h2本月销售额/h2 span classbadge较上月 12.5%/span /div div classcard-body canvas idprogressRing width140 height140/canvas div classsummary p classamount¥ 86,200/p p classdesc目标完成率 86%/p button idactionBtn classround-btn查看详情/button /div /div div classcard-footer 统计周期2025年1月 /div /div script srcjs/main.js/script /body /html4.2 编写样式css/style.css内容如下body { font-family: Microsoft YaHei, PingFang SC, sans-serif; background: #f0f2f5; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; } .data-card { width: 420px; background: #ffffff; border-radius: 20px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08); overflow: hidden; } .card-header { display: flex; justify-content: space-between; align-items: center; padding: 24px 24px 0; } .card-header h2 { margin: 0; font-size: 18px; color: #333333; } .badge { background: #f6ffed; color: #52c41a; font-size: 12px; font-weight: 600; padding: 4px 12px; border-radius: 999px; } .card-body { display: flex; align-items: center; justify-content: space-around; padding: 24px; } .summary { text-align: center; } .amount { margin: 0; font-size: 26px; font-weight: 700; color: #1f1f1f; } .desc { margin: 8px 0 16px; font-size: 13px; color: #999999; } .round-btn { appearance: none; border: none; background: #4a90d9; color: #ffffff; font-size: 14px; padding: 10px 28px; border-radius: 999px; cursor: pointer; transition: background 0.2s ease; } .round-btn:hover { background: #3a7bc8; } .card-footer { padding: 16px 24px; border-top: 1px solid #f0f0f0; font-size: 13px; color: #999999; }这里用到了三种不同的圆角卡片整体border-radius: 20px让四个角都圆润。右上角标签border-radius: 999px形成胶囊形状。按钮border-radius: 999px形成圆角按钮。胶囊形状的原理是当圆角半径足够大大到接近元素高度一半时两侧就会被拉成半圆。4.3 编写 Canvas 绘制逻辑js/main.js负责绘制圆角进度环。进度环本质上是一个圆弧其两端通过lineCap: round让线帽变成圆形从而呈现圆角效果。const canvas document.getElementById(progressRing); const ctx canvas.getContext(2d); // 圆环配置 const centerX canvas.width / 2; const centerY canvas.height / 2; const radius 55; const startAngle -Math.PI / 2; const endAngle Math.PI * 1.5; const progress 0.86; // 清理画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 背景圆环 ctx.beginPath(); ctx.arc(centerX, centerY, radius, 0, Math.PI * 2); ctx.strokeStyle #F0F2F5; ctx.lineWidth 12; ctx.stroke(); // 进度圆环 ctx.beginPath(); ctx.arc(centerX, centerY, radius, startAngle, startAngle Math.PI * 2 * progress); ctx.strokeStyle #4A90D9; ctx.lineWidth 12; ctx.lineCap round; ctx.stroke(); // 中间文字 ctx.fillStyle #1F1F1F; ctx.font bold 22px Microsoft YaHei, sans-serif; ctx.textAlign center; ctx.textBaseline middle; ctx.fillText(86%, centerX, centerY);解释一下关键部分startAngle -Math.PI / 2表示从时钟 12 点方向开始。进度圆环的结束角度为startAngle Math.PI * 2 * progressprogress 0.86表示画 86% 的圆弧。lineCap round让圆弧两个端点变成圆头这是进度环“圆角”的关键。如果你希望圆环支持动画可以用requestAnimationFrame让 progress 从 0 逐渐变到 0.86let currentProgress 0; const targetProgress 0.86; function animate() { currentProgress 0.01; if (currentProgress targetProgress) { currentProgress targetProgress; } ctx.clearRect(0, 0, canvas.width, canvas.height); // 背景圆环 ctx.beginPath(); ctx.arc(centerX, centerY, radius, 0, Math.PI * 2); ctx.strokeStyle #F0F2F5; ctx.lineWidth 12; ctx.stroke(); // 进度圆环 ctx.beginPath(); ctx.arc(centerX, centerY, radius, startAngle, startAngle Math.PI * 2 * currentProgress); ctx.strokeStyle #4A90D9; ctx.lineWidth 12; ctx.lineCap round; ctx.stroke(); ctx.fillStyle #1F1F1F; ctx.font bold 22px Microsoft YaHei, sans-serif; ctx.textAlign center; ctx.textBaseline middle; ctx.fillText(Math.round(currentProgress * 100) %, centerX, centerY); if (currentProgress targetProgress) { requestAnimationFrame(animate); } } animate();给按钮绑定一个简单的点击事件const btn document.getElementById(actionBtn); btn.addEventListener(click, function () { alert(圆角曲线实战示例点击查看详情); });4.4 运行与验证在浏览器中打开index.html预期效果如下页面中央显示一张白色卡片四角为 20 像素圆角。卡片右上角有一个绿色胶囊标签。卡片左侧是蓝色圆角进度环环内显示“86%”。卡片右侧显示金额、描述和圆角按钮。点击按钮会弹出提示框。如果页面白屏先按 F12 打开开发者工具查看 Console 是否有报错。最常见的问题是路径错误比如 CSS 或 JS 文件没有成功加载。5. 常见问题与排查思路在实际开发中创建圆角曲线经常遇到一些问题。下面整理成表格你可以直接对照排查。问题现象常见原因解决思路ctx.roundRect is not a function浏览器版本过低不支持roundRectAPI使用arcTo封装兼容函数或升级现代浏览器border-radius不生效元素是行内元素样式被覆盖属性拼写错误检查元素display是否为block/inline-block用开发者工具查看生效样式Canvas 画布显示空白canvas 宽高为 0获取ctx失败绘制前没有beginPath确认 HTML 中 canvas 标签有宽高属性且getContext(2d)返回正常圆角显示为直角半径超过元素宽高一半被浏览器忽略或radii传入了负数用Math.min限制半径确保半径符合规范进度环两端不是圆头未设置ctx.lineCap round在绘制进度圆环前设置lineCap高清屏上圆角锯齿明显Canvas 没有做 DPR 适配导致绘制模糊使用 devicePixelRatio 放大画布再通过 CSS 缩放贝塞尔曲线方向不对控制点坐标设错导致曲线反向弯曲在纸上画出起点、控制点、终点位置确认控制点位于目标弯曲一侧动画进度环在低端设备上卡顿每帧重复绘制大圆环性能开销高将静态背景圆环单独缓存到离屏 canvas或降低重绘频率其中最值得强调的一点是roundRect的兼容性。如果你的项目需要支持 IE 或者旧版浏览器建议统一使用arcTo封装函数而不是直接调用roundRect这样可以避免上线后出现兼容问题。6. 最佳实践与工程建议6.1 统一管理圆角值页面里圆角过多时很容易出现数值混乱。建议在项目里定义一组圆角变量形成设计规范。CSS 中使用变量:root { --radius-sm: 4px; --radius-md: 8px; --radius-lg: 16px; --radius-round: 999px; } .card { border-radius: var(--radius-lg); } .btn { border-radius: var(--radius-round); }JavaScript 中同样可以抽取常量const RADIUS { sm: 4, md: 8, lg: 16, round: 999 };这样当设计规范调整时只需要改一个地方不用全局搜索替换。6.2 Canvas 高清屏适配Canvas 绘制的圆角曲线在高分屏上有时会显得模糊。原因是 Canvas 的默认像素尺寸和 CSS 显示尺寸不一致。常见的做法是用devicePixelRatio进行缩放const canvas document.getElementById(progressRing); const dpr window.devicePixelRatio || 1; const width 140; const height 140; canvas.width width * dpr; canvas.height height * dpr; canvas.style.width width px; canvas.style.height height px; const ctx canvas.getContext(2d); ctx.scale(dpr, dpr);之后所有绘制逻辑仍按 140 的逻辑尺寸计算但实际渲染像素密度提高了圆角边缘会更清晰。6.3 注意性能与重绘范围如果是动画圆环或反复更新的图表需要关注重绘性能。能静态不变的图形尽量缓存到离屏 canvas每帧只重绘变化部分。计算坐标时也要避免在动画循环里频繁创建对象和字符串。6.4 圆角不能滥用视觉上圆角很讨喜但并不是所有地方都适合大圆角。数据报表、后台管理页面中正文区域和表格通常保留小圆角或直角避免界面显得过于“卡片化”影响信息浏览效率。建议遵循界面层级重要操作按钮用大圆角吸引注意复杂信息容器用小圆角保持克制。6.5 测试不同圆角半径下的边界情况写drawRoundRect这类通用函数时一定要测试边界情况半径等于宽高一半应该形成椭圆或胶囊。半径大于宽高一半函数需要自动收敛。宽高为 0 时不应该报错。负半径需要被拦截或转换为 0。这一条建议看似简单但在真实项目中很容易因为传入动态半径导致绘制异常提前做防御能减少很多线上问题。7. 总结与延伸学习本文从 CSS 的border-radius讲起接着深入 Canvas 的roundRect、arcTo、二次贝塞尔曲线和三次贝塞尔曲线最后完成了一个圆角数据卡片和圆角进度环的综合案例。读完并亲手实践之后你已经掌握了如何用 CSS 给普通元素设置圆角、椭圆角和胶囊角。如何用 Canvas 原生 API 绘制圆角矩形。如何用arcTo实现兼容旧浏览器的圆角矩形函数。如何理解贝塞尔曲线的起点、控制点和终点之间的关系。如何把圆角曲线应用到进度环、标签、按钮等真实场景中。遇到roundRect is not a function等常见报错时如何快速定位原因。下一步可以继续学习 SVG 的path绘制方式它对圆角曲线的描述和 Canvas 类似但在缩放、事件绑定、动画方面有独自的优势。也可以研究 Canvas 与动画库的结合比如用requestAnimationFrame做圆角进度环的缓动效果。如果对图形学感兴趣贝塞尔曲线的数学原理、线段求交、路径裁剪是更深入的方向但日常前端开发中先熟练掌握本文这些 API 已经能覆盖绝大多数圆角绘制需求。建议你打开编辑器把案例代码亲手敲一遍然后尝试修改圆角半径、进度值、卡片颜色观察不同的视觉效果。动手实践比只看文章理解得更牢。如果本文对你有帮助可以收藏备用或者分享给同样在学 Canvas 的朋友。

相关新闻