Konva 支持 动态 webp 图片
自从用了konva一直纠结图片引用动图的问题,目前有两个设想;
- 解析图片,逐帧播放;
- 再画板上面放一层dom ,将图片视频等相对定位上面;
这里我用的第一种方式,第二种感觉应该也可以,后面有时间再尝试,gif格式的图片,因为官方给了个例子所以很好弄,这里简单看下:
// index.html
<script src="/gifler.min.js"></script>
// 使用
const gifler = window.gifler
// 处理GIF
if(src.indexOf('.gif')>-1){
const canvas = document.createElement('canvas')
canvas.width = dom.naturalWidth
canvas.height = dom.naturalHeight
const gif = gifler(src)
gif.frames(canvas, (ctx: CanvasRenderingContext2D, frame: {
buffer: HTMLCanvasElement
}) => {
ctx.drawImage(frame.buffer, 0, 0)
layer?.batchDraw()
})
node.image(canvas);
}
使用webp,用插件先解析图片,然后,获取每一帧,再利用 requestAnimationFrame 播放,这里涉及到一个问题,就是在销毁node的时候,要取消掉 requestAnimationFrame 所以做了一个存和销毁的方法,切记要销毁!!
修改vite.config.ts
server: {
// 配置 wasm 文件的 MIME 类型
fs: {
strict: false,
},
},
// 优化依赖,排除 @jsquash/webp 让其自行处理 wasm
optimizeDeps: {
exclude: ['@jsquash/webp'],
},
// 配置 worker 格式
worker: {
format: 'es',
},
安装解析工具
npm i @jsquash/webp @1.5.0-animated-webp-support
//
"@jsquash/webp": "^1.5.0-animated-webp-support",
创建一个class方法
import Konva from "konva";
import { decodeAnimated } from "@jsquash/webp";
// @ts-ignore
const gifler = window.gifler
// 获取图片
export class ImgGetTool {
static readonly name = 'ImgGetTool'
// 存储动画帧ID,用于销毁 - 使用唯一ID作为键
animationFrames: Map<string, number> = new Map()
// 存储canvas与唯一ID的映射
canvasToIdMap: Map<HTMLCanvasElement, string> = new Map()
constructor() {}
// 加载图片 返回图片dom
public loadImgSrcReDom = (src: string): Promise<undefined | HTMLImageElement> => {
return new Promise(resolve => {
if (!src) {
resolve(undefined)
return undefined
}
const imageObj1 = new Image();
imageObj1.src = src;
imageObj1.onload = function () {
resolve(imageObj1)
};
})
}
// 判断是不是动画图(gif或webp)并加载
async getAnimatedDom(dom:any,layer:Konva.Layer){
if(dom){
let src = dom.getAttribute('src')
if(src){
// 处理GIF
if(src.indexOf('.gif')>-1){
const canvas = document.createElement('canvas')
canvas.width = dom.naturalWidth
canvas.height = dom.naturalHeight
const gif = gifler(src)
gif.frames(canvas, (ctx: CanvasRenderingContext2D, frame: {
buffer: HTMLCanvasElement
}) => {
ctx.drawImage(frame.buffer, 0, 0)
layer?.batchDraw()
})
return {
type:true,
canvas:canvas
}
}
// 处理WebP - 使用 @jsquash/webp 库解析成多帧然后播放
if(src.indexOf('.webp')>-1){
const canvas = await this.getWebpCanvas(src, dom, layer)
// 返回Canvas对象
return {
type:true,
canvas:canvas
}
}
}
return {
type:false,
canvas:dom
}
}
return {
type:false,
canvas:''
}
}
// 处理webp
private async getWebpCanvas(src:any, dom:any, layer:any){
const canvas = document.createElement('canvas')
// 生成唯一ID
const uniqueId = `webp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
// 存储canvas与唯一ID的映射
this.canvasToIdMap.set(canvas, uniqueId)
// 失败的
const errHandle = () => {
// 失败时使用静态图片
canvas.width = dom.naturalWidth
canvas.height = dom.naturalHeight
const ctx = canvas.getContext('2d')
if (ctx) {
ctx.drawImage(dom, 0, 0)
layer?.batchDraw()
}
}
// 获取WebP文件并使用 @jsquash/webp 解析帧
await fetch(src)
.then(res => res.arrayBuffer())
.then(async (buffer) => {
try {
// 使用 @jsquash/webp 解码动画帧
const frames:any = await decodeAnimated(buffer)
if (frames && frames.length > 0) {
// 设置Canvas尺寸为第一帧的尺寸
canvas.width = frames[0].imageData.width
canvas.height = frames[0].imageData.height
const ctx = canvas.getContext('2d')
if (ctx) {
let currentFrame = 0
let lastTime = 0
// 播放动画帧
const playFrame = (timestamp: number) => {
if (!lastTime) lastTime = timestamp
const elapsed = timestamp - lastTime
const frame:any = frames[currentFrame]
// 使用帧的duration来控制播放速度(duration是毫秒)
const frameDuration = frame.duration || 100
// 检查是否需要切换到下一帧
if (elapsed >= frameDuration) {
// 绘制当前帧的图像数据
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.putImageData(frame.imageData, 0, 0)
layer?.batchDraw()
// 切换到下一帧
currentFrame = (currentFrame + 1) % frames.length
lastTime = timestamp
}
// 存储动画帧ID
const frameId = requestAnimationFrame(playFrame)
// 存储到动画帧Map中,使用唯一ID作为键
this.animationFrames.set(uniqueId, frameId)
}
// 开始播放
const initialFrameId = requestAnimationFrame(playFrame)
// 存储初始动画帧ID,使用唯一ID作为键
this.animationFrames.set(uniqueId, initialFrameId)
}
} else {
errHandle()
}
} catch (err) {
errHandle()
}
})
.catch(() => {
errHandle()
})
return canvas
}
// 销毁动画
public destroyAnimation(canvas: HTMLCanvasElement) {
// 通过canvas找到唯一ID
const uniqueId = this.canvasToIdMap.get(canvas)
if (uniqueId) {
// 取消动画帧
const frameId = this.animationFrames.get(uniqueId)
if (frameId) {
cancelAnimationFrame(frameId)
this.animationFrames.delete(uniqueId)
}
// 从映射中移除
this.canvasToIdMap.delete(canvas)
}
}
// 销毁所有动画
public destroyAllAnimations() {
// 取消所有动画帧
this.animationFrames.forEach((frameId) => {
cancelAnimationFrame(frameId)
})
this.animationFrames.clear()
// 清空canvas到ID的映射
this.canvasToIdMap.clear()
// 清空缓存
this.imgNodeGifMap = {}
}
}
使用时候就是 new class ,测试时候发现性能还可以,后面有什么建议,望大家指点,感谢点赞关注!