使用JS创建线性渐变的图片

126 阅读3分钟

image.png 线性渐变的实现现在已经有很多种方式了,比如使用css 或者canvas 都可以实现,但是他们是如何创建的呢?我们可以不可以通过JS 也创建一个线性渐变的图片呢?

前置

首先肯定绕不开ArrayBuffer,ArrayBuffer 可以为我们创建一个固定长度的缓存区,这里的长度单位是字节数,但是如何想要对这段缓存区进行操作,那就必须使用DataView, unit8Array 等具体的操作类了,其实本质区别就是计算系统里面1byte = 8bit 8bit 可以表示的最大数值是255, 当某一个字符代表的unicode 大于255 了就会使用多个字节来表示,比如我们的汉字就有可能占两个或者三个字节,所以unit8Array,所以unit16Array等就表示对不同字节数的处理比如

const buffer = new ArrayBuffer(2); // 定义了两个字节长度的buffer
const dataview = new DataView(buffer);
dataview.setUint8(0, 8);
dataview.getUint8(0); // 值为8
dataview.setUint8(0, 256);
dataview.getUint8(0); // 因为大于255 值会被截取所以值输入为0
/**
 * 因为Unit32 四个字节超出buffer 定义的长度 所以操作的时候就会报错:
 * Uncaught RangeError: Offset is outside the bounds of the DataView
 */
dataview.setUint32(0, 1);

了解完上面的知识我们就可以为图片创建arraybuffer 后面用来装载我们的图片数据

const width = 40; // 40像素
const height = 40; 
const buffer = new ArrayBuffer(width * height * 4); // 一个像素需要4个字节
const uint8 = new Uint8Array(buffer);

上面提到了一个像素需要4个字节,这个是因为每个像素的颜色(rgba)都由红绿蓝变换生成每一个占一个字节8bit 所以它的取值范围是0~255,而第四个字节则是用来表示透明通道,接下就是就是计算了 算出每个颜色通道除以图片的宽度便可以算出当前像素到下一个像素的增长值,从而就可以填充buffer 里面的值

    const buffer = new ArrayBuffer(width * height * 4);
    const clam = new Uint8ClampedArray(buffer);
    const [sr,sg, sb] = startColor;
    const [er,eg, eb] = endColor;

    const gStep = (eg - sg) / width;
    const rStep = (er - sr) / width;
    const bStep = (eb - sb) / width;

    let j = 0;
    let offset = 0;
    while (j < height) {
        let i = 0;
        while (i < width) {
            clam[offset] = Math.round(sr + i * rStep);
            clam[offset + 1] = Math.round(sg + i * gStep);
            clam[offset + 2] = Math.round(sb + i * bStep);
            clam[offset + 3] = 255;
            i ++;
            offset = offset + 4;
        }
        j ++;
    }

组装完数据之后就可以使用canvas createImageData 和putImageData 将数据绘制到canvas 生成渐变的图片的

完整代码
function gradient(startColor, endColor, width, height) {
    const buffer = new ArrayBuffer(width * height * 4);
    const clam = new Uint8ClampedArray(buffer);
    const len = buffer.byteLength;
    const [sr,sg, sb] = startColor;
    const [er,eg, eb] = endColor;

    const gStep = (eg - sg) / width;
    const rStep = (er - sr) / width;
    const bStep = (eb - sb) / width;

    let j = 0;
    let offset = 0;
    while (j < height) {
        let i = 0;
        while (i < width) {
            clam[offset] = Math.round(sr + i * rStep);
            clam[offset + 1] = Math.round(sg + i * gStep);
            clam[offset + 2] = Math.round(sb + i * bStep);
            clam[offset + 3] = 255;
            i ++;
            offset = offset + 4;
        }
        j ++;
    }

    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;

    const ctx = canvas.getContext('2d');
    const imageData = ctx.createImageData(width, height);
    imageData.data.set(clam);
    ctx.putImageData(imageData, 0, 0)
    document.body.append(canvas);
 }