- Article -

CANVAS生成海报和PROMISE的思考(二)

分类于 前端开发 标签 CANVAS Promise 发表于2020-02-22 20:00

画海报

如果要在Canvas将所有图片画在一起,那么我们得保证所有图片都加载完毕之后才开始,可以使用Promise.all来完成全部图片的异步加载。那么就需要一个方法来将图片通过URl加载出来。

const buildImgInstance = (url) => {
  let image = new Image();
  image.setAttribute('crossOrigin', 'anonymous');
  image.src = url ;
  return new Promise((resolve, reject) => {
    image.onload = () => {
      resolve(image);
    };
    image.onabort = e => {
      reject(e);
    };
    image.onerror = e => {
      reject(e);
    }
  });
};

因此接下来可以使用Promise.all来保证多张图片加载完成。

export default function drawImages(r) {
  let imageInstances = [buildImgInstance(r.backgroundUrl)];
  for (let i=0;i<r.images.length;i++){
    imageInstances.push(buildImgInstance(r.images[i].url));
  }
  return new Promise((resolve, reject) => {
    Promise.all(imageInstances).then(Instances => {
      const canvas = document.createElement('canvas');
      let bgWidth = r.hasOwnProperty('width') ? r.width : Instances[0].width;
      let bgHeight = r.hasOwnProperty('height') ? r.height : Instances[0].height;
      canvas.width = bgWidth;
      canvas.height = bgHeight;
      canvas.backgroundColor = "rgb(255, 255, 255)";
      const context = canvas.getContext('2d');
      context.drawImage(Instances[0], 0, 0, bgWidth, bgHeight);
      for (let i= 1;i<Instances.length;i++){
        let imgAttr = r.images[i-1];
        let width = imgAttr.hasOwnProperty('width') ? imgAttr.width : Instances[i].width;
        let height = imgAttr.hasOwnProperty('height') ? imgAttr.height : Instances[i].height;
        context.drawImage(Instances[i], imgAttr.x, imgAttr.y, width , height );
      }
      for (let i= 0;i<r.texts.length;i++){
        context.save();
        if(r.texts[i].hasOwnProperty('attr')){
          for (let a in r.texts[i].attr) {
            context[a] = r.texts[i].attr[a];
          }
        }
        context.fillText(r.texts[i].title,r.texts[i].x,r.texts[i].y);
        context.restore();
      }
      resolve(canvas.toDataURL('image/jpeg', 0.6));
    }).catch(err=>{
      reject(err)
    });
  });
}

这是封装好的一个方法,我们配合传入的参数来看

{
   width:750,
   height:100,
   backgroundUrl:"https://img.url",
   images:[{url:"https://img.url",x:1010,y:120,width:10,height:55},{url:"https://img.url",x:50,y:0},{url:"https://img.url",x:100,y:3000}],
   texts:[{title:"文案",x:1010,y:2010,attr:{font:"100px Helvetica"}}]
}

将参数传进去,方法会将里面全部的图片通过异步去加载,当所有图片加载完成的时候,就会将images和texts里面的图片和文案按照规定往背景图上面贴,然后返回一个Promise对象,只要任何一张图片加载失败,就能触发catch方法。参数里面针对图片的宽高都可以省略,如果省略,将使用图片的实际宽高来绘制。

注意的是,所有图片都得允许跨域。

promise链式调用

然后,我们来看看一个和上面无关的问题。

Promise 的链式调用。也就是 promise().then().then().catch() 的形式,然后如何在某一个 then() 里面 跳出 Promise。

Promise中,只要返回了一个 promise 对象,如果 promise 对象不是 Rejected 或 Fulfilled 状态,then 方法就会继续调用。利用这个特性,可以处理多个异步逻辑。但有时候某个 then 方法的执行结果可能会决定是否需要执行下一个 then,这个时候就需跳出 promise,主要思想就是使用 reject 来中止 promise 的 then 继续执行。

回顾下实例化的 promise 对象有以下三个状态:

let p1 = new Promise((resolve, reject) => {
        setTimeout(resolve, 10, "p1->resolve");
        setTimeout(reject, 1000, "p1->reject");
      });

      p1.then(r => {
        console.log("1.then", r);
        return new Promise((resolve, reject) => {
          setTimeout(resolve, 1100, "p2->resolve");
          setTimeout(reject, 100, "p2->reject(err)");
        });
      })
        .then(r => {
          console.log("2.then", r);
        })
        .catch(r => {
          console.log("catch", r);
        });

不难看出,如果要跳出链式的Then 可以在then里调用reject即可。

return Promise.reject("逻辑上触发 catch")