前言:昨天折腾半天解决canvas绘制的图表模块,在向下滚动的时候内容直接溢出遮挡正常元素,然后找解决办法找了半天没解决,最后在尝试真机预览的时候,没有溢出,画布渲染在滑动页面时没有溢出?显示正常?
一、提出问题
在小程序中使用canvas绘制的内容,默认就会强制覆盖其他正常的元素,如何让绘制的内容一直保持在它的外层容器之中不溢出?【1.canvas绘制的内容为什么真机正常、预览溢出? 2. 如何兼容真机和预览画布内容不溢出(我差点想放弃了,反正真机显示没问题...)】
二、对比web开发中和小程序中使用canvas的区别之处
1.正常的 Web开发中: Canvas:
- 遵循正常的 CSS 层叠上下文
- overflow: hidden 可以裁剪
2.在微信小程序 中的Canvas:
- 使用原生组件渲染(独立于 WebView)
- 层级高于普通 view
- 在某些情况下会"穿透"父容器的 overflow 裁剪
- 绘制的canvas内容,不做任何处理的情况下:画布内容溢出,页面滚动直接遮挡其他正常元素...
三、解决小程序中,使用canvas绘制内容后,长页面滚动溢出及遮挡其他元素问题
解决方案:三层防护策略
第一层:CSS 容器约束(基础防护)
bash
.chart-container {
width: 100%;
height: 160px;
background-color: #f8fafc;
border-radius: 12px;
overflow: hidden; /* 不推荐: 在小程序中可能不完全生效 */
position: relative; /*正确: 创建定位上下文 */
}
.emotion-chart {
width: 100%;
height: 100%;
display: block; /* 正确: 消除默认间距 */
}
作用:
- overflow: hidden 在真机上通常有效
- position: relative 为可能的绝对定位子元素提供参考
- display: block 消除 canvas 的 inline 默认行为
第二层:绘制边界控制(核心防护)
bash
// ✅ 关键:所有绘制内容都在 padding 安全区域内
const padding = { top: 20, right: 20, bottom: 30, left: 50 };
const chartWidth = width - padding.left - padding.right;
const chartHeight = height - padding.top - padding.bottom;
// 1. 清空画布时填充整个区域
ctx.fillStyle = '#f8fafc';
ctx.fillRect(0, 0, width, height); // ✅ 覆盖整个 canvas
// 2. 网格线在安全区域内
ctx.moveTo(padding.left, y);
ctx.lineTo(width - padding.right, y); // ✅ 不超出边界
// 3. Y 轴标签在左侧安全区
ctx.fillText(emojis[i] || '', padding.left - 8, y); // ✅ 虽然超出图表区,但仍在 canvas 内
// 4. 折线和数据点在图表区内
ctx.arc(point.x, point.y, 6, 0, Math.PI * 2); // ✅ point.x 在 [padding.left, width-padding.right] 范围内
// 5. X 轴标签在底部安全区
ctx.fillText(week, x, height - padding.bottom + 8); // ✅ 在 bottom padding 内
作用:
bash
┌─────────────────────────────────────┐
│ Canvas 整个区域 │
│ ┌─────────────────────────────┐ │
│ │ padding.top (20px) │ │
│ │ 😊 ─ ─ ─ ─ ─ ─ ─ │ │
│ │ 😐 ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ │
│ │ 😟 ─ ─ ─ ─ ─ ─ ─ │ │
│ │ padding.left 图表区 │ │
│ │ (50px) (安全区域) │ │
│ │ 折线图 │ │
│ │ 数据点 │ │
│ │ │ │
│ │ 第 1 天 第 2 天... │ │
│ │ padding.bottom (30px) │ │
│ └─────────────────────────────┘ │
│ padding.right (20px) │
└─────────────────────────────────────┘
所有绘制内容都在 Canvas 物理边界内!
第三层:背景色覆盖(辅助防护)
bash
// 每次绘制前先填充背景色
ctx.fillStyle = '#f8fafc';
ctx.fillRect(0, 0, width, height);
作用:
- 确保每次重绘时清除上一帧的内容
- 即使有轻微溢出,背景色也能覆盖
- 防止残留的绘制痕迹
四、回头梳理细节
1.关键原理:
bash
❌ 错误做法:依赖 CSS 裁剪
Canvas 内容 → 溢出 → 期望 overflow: hidden 裁剪
结果:小程序中原生组件可能穿透 CSS 约束
✅ 正确做法:控制绘制边界
Canvas 内容 → 始终在 canvas 物理边界内绘制
结果:根本不会产生溢出,无需依赖 CSS 裁剪
2.对比说明:
-
可能溢出:bashconst padding = { left: 40 }; // ❌ 边距不足 // Y 轴标签绘制在 canvas 物理边界外 ctx.fillText('😊', padding.left - 12, y); // 实际 x 坐标:40 - 12 = 28 // 如果 emoji 宽度 20px,则从 x=8 开始绘制 // 可能超出 canvas 左边界! -
绝对安全:bashconst padding = { left: 50 }; // ✅ 增加边距 // Y 轴标签仍在 canvas 物理边界内 ctx.fillText('😊', padding.left - 8, y); // 实际 x 坐标:50 - 8 = 42 // emoji 从 x=42 开始绘制,完全在 canvas 内 -
图例演示:
bash┌─────────────────────────────────┐ │ WebView (普通 view 层) │ │ ┌─────────────────────────┐ │ │ │ .chart-container │ │ │ │ (overflow: hidden) │ │ │ └─────────────────────────┘ │ └─────────────────────────────────┘ ↓ (可能穿透) ┌─────────────────────────────────┐ │ 原生组件层 (Canvas) │ │ ┌─────────────────────────┐ │ │ │ <canvas type="2d"> │ │ │ │ 所有绘制内容在这里 │ │ │ └─────────────────────────┘ │ └─────────────────────────────────┘
3.真机和微信开发者工具:
- 区别:
真机时微信的原生渲染,严格准许父容器约束,overflow的处理有效;
微信开发者工具,基于Chrome 浏览器 的环境,层次独立,容易失效;
-
最佳 - 推荐做法
bash// 1. 预留充足的安全边距 const padding = { top: 20, right: 20, bottom: 30, left: 50 // 为坐标轴预留空间 }; // 2. 所有绘制在安全区域内 ctx.fillText(text, padding.left - offset, y); // offset < padding.left // 3. 每次绘制前清空画布 ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, width, height); // 4. 使用 canvas 本身的背景色 // 而不是依赖父容器的背景 -
避免做法
bash// ❌ 依赖 CSS 裁剪 .chart-container { overflow: hidden; // 在小程序中不可靠 } // ❌ 绘制到 canvas 边界外 ctx.fillText(text, -10, y); // x 坐标为负 // ❌ 边距不足 const padding = { left: 10 }; // 不够放标签 ctx.fillText(text, padding.left - 5, y); // 溢出! -
调试技巧
bash// 在开发时绘制安全区域边界(调试用) ctx.strokeStyle = 'red'; ctx.lineWidth = 1; ctx.strokeRect(padding.left, padding.top, chartWidth, chartHeight); // 绘制 canvas 物理边界 ctx.strokeStyle = 'blue'; ctx.strokeRect(0, 0, width, height); // 发布前移除调试代码 -
回头总结: 不依赖 overflow: hidden 来裁剪溢出内容,而是通过 精确控制绘制边界 ,确保 Canvas 的所有内容从一开始就绘制在安全区域内。这是最根本、最可靠的解决方案!
五、案例的完整代码
见下一篇,完整代码
