如何让HTML5 Canvas旋转渐变围绕中心完全覆盖画布?
实现任意角度下完全覆盖Canvas的线性渐变
我需要实现一个无论角度如何都能完全覆盖HTML5 Canvas的线性渐变效果,参考相关方法后发现存在问题——画布无法被渐变完全覆盖。我修改了代码,用黄色区域标记本应被黑白渐变覆盖的区域(正常情况下黄色应该完全不可见),同时用绿色点绘制了渐变的控制点,正确计算的话这些点应该延伸到画布边缘。
问题复现代码
HTML
<canvas id="canvas" width="300" height="200"></canvas>
CSS
canvas { border : 2px solid red; }
JavaScript
var ctx = canvas.getContext("2d"); var w = canvas.width; var h = canvas.height; function bestFitGradient(angle){ var dist = Math.sqrt(w * w + h * h) / 2; // 获取对角线长度的一半 var diagAngle = Math.asin((h / 2) / dist); // 获取对角线对应的角度 // 角度对称处理,转到第一象限 var a1 = ((angle % (Math.PI *2))+ Math.PI*4) % (Math.PI * 2); if(a1 > Math.PI){ a1 -= Math.PI } if(a1 > Math.PI / 2 && a1 <= Math.PI){ a1 = (Math.PI / 2) - (a1 - (Math.PI / 2)) } // 计算渐变方向及右侧到边缘的角度 var ang1 = Math.PI/2 - diagAngle - Math.abs(a1); var ang2 = Math.abs(diagAngle - Math.abs(a1)); // 获取中心到水平和垂直边缘的距离 var dist1 = Math.cos(ang1) * h; var dist2 = Math.cos(ang2) * w; // 取最大距离 var scale = Math.max(dist2, dist1) / 2; // 计算渐变起点和终点的向量 var dx = Math.cos(angle) * scale; var dy = Math.sin(angle) * scale; var x0 = w / 2 + dx; var y0 = h / 2 + dy; var x1 = w / 2 - dx; var y1 = h / 2 - dy; // 创建渐变 const g = ctx.createLinearGradient(x0, y0, x1, y1); // 添加颜色节点 g.addColorStop(0, "yellow"); g.addColorStop(0, "white"); g.addColorStop(.5, "black"); g.addColorStop(1, "white"); g.addColorStop(1, "yellow"); return { g: g, x0: x0, y0: y0, x1: x1, y1: y1 }; } function update(timer){ var r = bestFitGradient(timer / 1000); // 绘制渐变 ctx.fillStyle = r.g; ctx.fillRect(0,0,w,h); // 绘制控制点 ctx.lineWidth = 3; ctx.fillStyle = '#00FF00'; ctx.strokeStyle = '#FF0000'; ctx.beginPath(); ctx.arc(r.x0, r.y0, 5, 0, 2 * Math.PI, false); ctx.stroke(); ctx.fill(); ctx.beginPath(); ctx.arc(r.x1, r.y1, 5, 0, 2 * Math.PI, false); ctx.stroke(); ctx.fill(); requestAnimationFrame(update); } requestAnimationFrame(update);
问题分析与修正方案
原代码的核心问题在于渐变端点的计算逻辑错误,导致渐变覆盖范围不足。正确思路是:计算画布四个顶点到渐变方向线的垂直投影长度,取最大值作为渐变需要延伸的长度,确保渐变能覆盖整个画布。
修正后的JavaScript代码
var ctx = canvas.getContext("2d"); var w = canvas.width; var h = canvas.height; function bestFitGradient(angle) { // 计算渐变方向的单位向量 const dirX = Math.cos(angle); const dirY = Math.sin(angle); // 画布四个顶点的坐标 const points = [ {x: 0, y: 0}, {x: w, y: 0}, {x: w, y: h}, {x: 0, y: h} ]; // 计算每个顶点到渐变中心连线在渐变方向上的投影长度 let maxProjection = 0; const centerX = w / 2; const centerY = h / 2; for (const p of points) { const dx = p.x - centerX; const dy = p.y - centerY; // 计算投影长度(点积) const proj = dx * dirX + dy * dirY; maxProjection = Math.max(maxProjection, Math.abs(proj)); } // 计算渐变的起点和终点 const x0 = centerX + dirX * maxProjection; const y0 = centerY + dirY * maxProjection; const x1 = centerX - dirX * maxProjection; const y1 = centerY - dirY * maxProjection; // 创建渐变 const g = ctx.createLinearGradient(x0, y0, x1, y1); g.addColorStop(0, "white"); g.addColorStop(0.5, "black"); g.addColorStop(1, "white"); return { g: g, x0: x0, y0: y0, x1: x1, y1: y1 }; } function update(timer) { var r = bestFitGradient(timer / 1000); ctx.fillStyle = r.g; ctx.fillRect(0, 0, w, h); // 绘制控制点 ctx.lineWidth = 3; ctx.fillStyle = '#00FF00'; ctx.strokeStyle = '#FF0000'; ctx.beginPath(); ctx.arc(r.x0, r.y0, 5, 0, 2 * Math.PI, false); ctx.stroke(); ctx.fill(); ctx.beginPath(); ctx.arc(r.x1, r.y1, 5, 0, 2 * Math.PI, false); ctx.stroke(); ctx.fill(); requestAnimationFrame(update); } requestAnimationFrame(update);
关键修改说明
- 投影长度计算:通过计算画布四个顶点到渐变中心的向量在渐变方向上的投影,取最大绝对值作为渐变需要延伸的长度,确保渐变覆盖所有顶点。
- 简化角度处理:无需复杂的象限转换,直接基于原始角度计算方向向量,逻辑更清晰。
- 移除冗余颜色节点:删除了用于标记问题的黄色节点,渐变仅保留黑白过渡,确保完全覆盖画布。
内容的提问来源于stack exchange,提问作者Dady
相关产品推荐
相关产品推荐

