用canvas实现一个雪碧图制作工具

381 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。 点击查看活动详情

写在前面

前面我们实现了一个简单的canvas库 100行代码写个canvas库 现在我们试一下这个库实际用起来怎么样,能不能满足常规的开发要求,雪碧图是前端提升性能一个常见的操作了,原理就是把多张图片合成为一张,通过css定位来取到具体的图像,减少http的请求,达到优化的目的。但是我在使用的时候发现了一些不友好的地方(可能是我用的工具比较low)

  • 合成的时候不能调整某个图片的位置,我想把某几个同类型的图片放在一起
  • 某几个图片加进去之后我想删掉
  • 合成完之后又有新的图片要添加进去,进行编辑

所以我们自己来写个工具,实现这些功能。

cssssprite.gif

封装一个icon类

根据参数绘制图片

class Icon extends BasicElement {
    constructor(options) {
        this.x = options.x
        this.y = options.y
        this.w = options.w
        this.h = options.h
        this.offsetX = options.offsetX
        this.offsetY = options.offsetY
        this.image = new Image()
        this.image.src = options.src
    }
    // 结合icon的xywh和偏移量绘制图片
    draw(ctx) {
        ctx.drawImage(this.image, this.x + this.offsetX, this.y + this.offsetY, this.w, this.h)
    }
    // 点是否在icon图标上
    pointInElement(x, y) {
        return this.x + this.offsetX <= x && 
               this.y + this.offsetY <= y && 
               this.x + this.offsetX + this.w >= x && 
               this.y + this.offsetY + this.h >= y;
    }
    // 其他要配合Stage的省略,继承BasicElement即可
}

组合删除icon

每张图片都要内置一个删除按钮,放在图片的右上角,点击删除加到画布上的图片,因此我们在组合Container里放2个icon,定义一个新的类ImageElement

class ImageElement {
    constructor(options) {
        this.x = options.x
        this.y = options.y
        this.w = options.w
        this.h = options.h
        
        // 初始化一个容器
        this.container = new Container({
            x: this.x,
            y: this.y,
            w: this.w,
            h: this.h
        });
        
        // 直接初始化2个icon,一个放图片,一个作为删除按钮
        this.image = new Icon({
            offsetX: 0,
            offsetY: 0,
            w: this.w,
            h: this.h,
            src: options.src,
        })
        
        this.del = new Icon({
            // 定位到容器的右上角
            offsetX: this.w - 5,
            offsetY: -5,
            w: this.w,
            h: this.h,
            src: "./close.png",
        })
        // 给删除按钮添加点击事件,删除整个container,包括del
        this.del.addEvent("click", (t) => {
            this.container.destory()
        });
        
        this.container.add(this.image)
        this.container.add(this.del)
        
        return this.container
    }
}

这样一个自带删除和拖拽的图片组件就写好了,通过input选取图片,再通过FileReader拿到图片信息,初始化到画布上,最后页面随便放个按钮根据canvas信息生成一个代码和导出文件就可以了,看一下应用代码

这里解释一个如何完成编辑功能的,在第一次导出图片后,可以把每个图片的信息也生成导出,在后续编辑的时候只要把配置信息拿到就可以了(读取文件或者输入都可以)

let config = document.getElementById("configinfo").value;

如果有配置信息,在初始化图片的时候就还原一下就可以了。

let s2 = new Stage(document.getElementById("stage"));
let data = [];
document.getElementById("upload_file").addEventListener("change", function (e: any) {
    let files = e.target.files
    let config = document.getElementById("configinfo").value;
    config = JSON.parse(config || "[]")
    // 没有配置文件也可以,就当新增了
    for(let i = 0; i < files.length; i++) {
        let reader = new FileReader();
        reader.onloadstart = function () {};
        reader.readAsDataURL(files[i]);
        reader.onloadend = function (e) {
            var base64 = this.result;
            var image = new Image();
            image.src = base64;
            let cur = config.find(item => item.name == files[i].name)
            // 如果存在,就返显xy,否则初始化为0
            image.onload = function (r) {
                data.push({
                    name: files[i].name,
                    x: cur ? cur.x : 0,
                    y: cur ? cur.y : 0,
                    w: this.width,
                    h: this.height,
                    src: base64,
                });
                s2.add(
                    new ImageElement({
                        name: files[i].name,
                        x: cur ? cur.x : 0,
                        y: cur ? cur.y : 0,
                        w: this.width,
                        h: this.height,
                        src: base64,
                    })
                );
            };
        };
        reader.onerror = function () {};
    }
})