Canvas图像切片详解,掌握精灵图切片、九宫格切片、图集切片等实用技巧。图像切片是将大图分割成多个小图的技术,常用于精灵图、UI图集等场景。
源图像 (400x400)
+----+----+----+----+
| 0 | 1 | 2 | 3 | 每个 tile: 100x100
+----+----+----+----+
| 4 | 5 | 6 | 7 |
+----+----+----+----+
| 8 | 9 | 10 | 11 |
+----+----+----+----+
| 12 | 13 | 14 | 15 |
+----+----+----+----+
function sliceImage(img, tileWidth, tileHeight) {
const tiles = []
const cols = Math.floor(img.width / tileWidth)
const rows = Math.floor(img.height / tileHeight)
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const canvas = document.createElement('canvas')
canvas.width = tileWidth
canvas.height = tileHeight
const ctx = canvas.getContext('2d')
ctx.drawImage(
img,
col * tileWidth, row * tileHeight,
tileWidth, tileHeight,
0, 0,
tileWidth, tileHeight
)
tiles.push(canvas)
}
}
return tiles
}
class SpriteSheet {
constructor(image, frameWidth, frameHeight) {
this.image = image
this.frameWidth = frameWidth
this.frameHeight = frameHeight
this.cols = Math.floor(image.width / frameWidth)
this.rows = Math.floor(image.height / frameHeight)
}
drawFrame(ctx, index, x, y, scale = 1) {
const col = index % this.cols
const row = Math.floor(index / this.cols)
ctx.drawImage(
this.image,
col * this.frameWidth, row * this.frameHeight,
this.frameWidth, this.frameHeight,
x, y,
this.frameWidth * scale, this.frameHeight * scale
)
}
getFrameCount() {
return this.cols * this.rows
}
}
用于可拉伸的UI边框:
function draw9Slice(img, x, y, width, height, sliceSize) {
const s = sliceSize
const sw = img.width
const sh = img.height
// 左上角
ctx.drawImage(img, 0, 0, s, s, x, y, s, s)
// 右上角
ctx.drawImage(img, sw - s, 0, s, s, x + width - s, y, s, s)
// 左下角
ctx.drawImage(img, 0, sh - s, s, s, x, y + height - s, s, s)
// 右下角
ctx.drawImage(img, sw - s, sh - s, s, s, x + width - s, y + height - s, s, s)
// 上边
ctx.drawImage(img, s, 0, sw - 2 * s, s, x + s, y, width - 2 * s, s)
// 下边
ctx.drawImage(img, s, sh - s, sw - 2 * s, s, x + s, y + height - s, width - 2 * s, s)
// 左边
ctx.drawImage(img, 0, s, s, sh - 2 * s, x, y + s, s, height - 2 * s)
// 右边
ctx.drawImage(img, sw - s, s, s, sh - 2 * s, x + width - s, y + s, s, height - 2 * s)
// 中间
ctx.drawImage(img, s, s, sw - 2 * s, sh - 2 * s, x + s, y + s, width - 2 * s, height - 2 * s)
}
class AtlasManager {
constructor() {
this.tiles = new Map()
}
addTile(name, image, x, y, width, height) {
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
const ctx = canvas.getContext('2d')
ctx.drawImage(image, x, y, width, height, 0, 0, width, height)
this.tiles.set(name, canvas)
}
draw(ctx, name, x, y, width, height) {
const tile = this.tiles.get(name)
if (tile) {
ctx.drawImage(tile, x, y, width || tile.width, height || tile.height)
}
}
getTile(name) {
return this.tiles.get(name)
}
}
根据需求动态切割图像:
function dynamicSlice(img, regions) {
return regions.map(region => {
const canvas = document.createElement('canvas')
canvas.width = region.width
canvas.height = region.height
const ctx = canvas.getContext('2d')
ctx.drawImage(
img,
region.x, region.y,
region.width, region.height,
0, 0,
region.width, region.height
)
return { name: region.name, canvas }
})
}
内存占用
大量切片会占用较多内存,按需加载。
缓存策略
频繁使用的切片应缓存到离屏Canvas。
边界处理
注意切片边界不要超出源图像范围。