200行代码带你制做实用的节日头像定制小工具【可在线制做,快来试试吧】

哈哈,趁着平安夜来凑凑热闹,其实好久以前就作过一个相似功能的毕业季生成邀请函小应用。这种有关图像合成的应用其实只须要熟悉h5中canvas的相关API和一点几何常识就ok了,是很容易实现的,这里就带你们探究一下实现的过程。再加上最近会有一大波节日到来,相信这个小工具值得你的拥有,若是以为不错,不如点个呗。也可关注个人公众号BRandF(有小工具的图文信息)推荐给朋友用哈,二维码在文末。html

效果

image

  1. 用户能上传图片
  2. 能将用户图片自动调整成合适的尺寸
  3. 能切换饰品
  4. 能切换头像图形边框
  5. 保存/下载生成的图片

在线定制头像

在线制做头像应用git

思路

  1. 初始化,在这个阶段会将预置的饰品和图形画到canvas上面去。

image

  1. 状态更改,在这个阶段用户能更改饰品,图形边框和上传图片,咱们须要将最新的用户图片,饰品图片,图形边框画到canvas中。

image

  1. 结果输出,最后将canvas的内容输出成可保存下载的图片。

image

嗯没错,思路是很是直观的。下面会对核心功能详细解析。github

实现

首先须要明确的是,既然要作头像,那么基本能够认为最终用户须要的图片是长和宽相等的。但实际上用户上传的图片极可能并不规整,这里有三种状况,宽大于高,宽小于高,宽等于高,同时又要等比放大or缩小,那应该怎么办呢?web

有一个很巧妙的办法就是,好比宽大于高时,咱们能够将原图的高等比缩放到canvas画布的高度,原图的宽也跟着等比缩放。就像下图,这样既能够保持原图的比例,canvas又不用留白,虽然会丢失掉原图的一小部分,可是从实际效果来看仍是能够接受的。好吧,作这一步就是为了替(tou)换(lan)掉用户自定义裁剪这个功能。sql

image

调整用户图片尺寸

// 不急,等下讲到这两个函数
const base64Url = await this.file2Base64(sourceImage);
const imgObj = await this.createImage(base64Url);

const CANVANS_SIZE = 256;
const type = imgObj.width - imgObj.height;

// 三种状况
if (type > 0) {
    // 不管宽大于高仍是宽小于高,都会进行等比缩放
    const w = imgObj.width * CANVANS_SIZE / imgObj.height;
    context.drawImage(imgObj, 0, 0, w, CANVANS_SIZE);
} else if (type < 0) {
    const h = imgObj.height * CANVANS_SIZE / imgObj.width;
    context.drawImage(imgObj, 0, 0, CANVANS_SIZE, h);
} else {
    context.drawImage(imgObj, 0, 0, CANVANS_SIZE, CANVANS_SIZE);
}
复制代码

图片转换成Base64格式

使用FileReader对象读取用户上传的文件,并转为base64格式。因为读取图片的过程是异步的,这里使用Promise封装了一下。canvas

file2Base64(domFile) {
    return new Promise((resolve, rejest) => {
        const reader = new FileReader();
        reader.readAsDataURL(domFile);
        reader.onload = (e) => {
            resolve(reader.result);
        };
    });
}
复制代码

建立图片

因为canvas绘制图片时只接受图片对象,获得base64格式的图片以后,须要再包装成图片对象才能绘制到canvas中。因为载入成图片对象的过程也是异步的,这里也使用Promise封装了一下后端

createImage(imgUrl) {
    return new Promise((resolve, rejest) => {
        const imgObj = new Image();
        imgObj.src = imgUrl;
        imgObj.onload = (e) => {
            resolve(imgObj);
        };
    });
}
复制代码

边框处理

就是如何在canvas上绘制几何图形,以为这个处理方式很不错,这里借鉴了一下绘制圆角和圆形的方法。 《在Canvas中绘制圆角矩形》安全

输出到画布

将异步操做封装成Promise的好处在这里就体现出来了,能很是直观的使用同步的编写形式将异步操做表达出来。要注意的是用户图片须要先绘制到canvas中,不然饰品和图形边框就会被图片覆盖。app

/** * @param {string} imgUrl (进过处理的用户图片url) * @param {object} decorationCurrent (饰品对象) * @returns imageUrl * @memberof App */
async handleMakeImage(imgUrl, decorationCurrent) {
    if (!(imgUrl || decorationCurrent)) { return ''; }
    const { border } = this.state;
    const { value } = border;
    const { source, style } = decorationCurrent;
    const { width, height, top, left } = style;
    const { canvas } = this.refs;
    this.clearCanvas(canvas);
    const context = canvas.getContext('2d');

    if (imgUrl) {
        const bgImg = await this.createImage(imgUrl);
        context.drawImage(bgImg, 0, 0, bgImg.width, bgImg.height);
    }
    this.drawBorder(value, context);
    if (decorationCurrent && source) {
        const imgObj = await this.createImage(source);
        context.drawImage(imgObj, left, top, width, height);
    }

    const targetUrl = canvas.toDataURL('image/png');
    return targetUrl;
}
复制代码

保存or下载

最后模拟一下a标签的点击事件,完成图片的下载。若是是移动端用户,长按图片便可保存图片。dom

downloadImage(url) {
    if (url) {
        const aLink = document.createElement('a');
        const evt = document.createEvent('HTMLEvents');
        evt.initEvent('click', true, true);
        // 指定图片文件名
        aLink.download = 'protrait.png';
        aLink.href = url;
        aLink.click();
    }
}
复制代码

缓解图片模糊的小细节

发现若是按正常的图片比例去载入图片,最后生成出来的图片会出现模糊。有一个简易的方法就是将canvas的长宽扩大到原来的两倍,但图片仍是按原来的比例来显示,简单的来讲就是将大图片缩小了显示。这样就能有效缓解图片模糊的问题了。

...
<canvas width="512" height="512" className="shadow d-n" ref="canvas" />
...
<img className="w256 h256" src={targetUrl} alt="" />
复制代码

ok,就这样,核心功能代码就是这些了,一共200行不到。

有兴趣的话能够看源码了解更多细节

源码&&在线应用

结束

因为接下来各类节日不断接近,这个小工具的素材也会不断更新,也欢迎你们贡献一些有趣的素材哈哈。

欢迎关注公众号获取这个小工具的图文信息

image

或者你感兴趣的内容

Re从零开始系列

web安全系列

相关文章
相关标签/搜索