纯前端Canvas绘制海报

封装类:

复制代码
/**
 * Canvas绘制海报
 */
class Poster {
  canvas: HTMLCanvasElement;
  context: CanvasRenderingContext2D;

  constructor(el: string, width: number, height: number) {
    const canvas = document.querySelector<HTMLCanvasElement>(el);
    if (canvas === null) {
      throw new Error("获取canvas失败");
    }
    canvas.width = width;
    canvas.height = height;
    this.canvas = canvas;

    const context = canvas.getContext("2d");
    if (context === null) {
      throw new Error("获取context失败");
    }
    this.context = context;
  }

  setFillStyle(style: string) {
    this.context.fillStyle = style;
    return this;
  }

  setStrokeStyle(style: string) {
    this.context.strokeStyle = style;
    return this;
  }

  setFont(font: string) {
    this.context.font = font;
    return this;
  }

  fillCanvas() {
    this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
    return this;
  }

  clearCanvas() {
    this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
    return this;
  }

  loadImage(src: string) {
    return new Promise<HTMLImageElement>((resolve, reject) => {
      const img = new Image();
      img.src = src;
      img.crossOrigin = "anonymous";
      img.onload = function () {
        resolve(img);
      };
    });
  }

  drawImage(image: CanvasImageSource, x: number, y: number, width: number, height: number) {
    this.context.drawImage(image, x, y, width, height);
    return this;
  }

  drawText(text: string, x: number, y: number, limitWidth?: number, lineHeight?: number) {
    if (limitWidth != undefined) {
      let measureTextWidth = 0;
      let lastSubstringIndex = 0;
      for (let i = 0; i < text.length; i++) {
        const metrics = this.context.measureText(text[i]);
        measureTextWidth += metrics.width;
        if (measureTextWidth > limitWidth) {
          this.context.fillText(text.substring(lastSubstringIndex, i), x, y);
          measureTextWidth = 0;
          lastSubstringIndex = i;
          y += lineHeight ?? metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
        }
        if (i === text.length - 1) {
          this.context.fillText(text.substring(lastSubstringIndex, i + 1), x, y);
        }
      }
    } else {
      this.context.fillText(text, x, y);
    }
    return this;
  }

  drawLine(x1: number, y1: number, x2: number, y2: number) {
    this.context.moveTo(x1, y1);
    this.context.lineTo(x2, y2);
    this.context.stroke();
    return this;
  }

  toDataURL(type?: string, quality?: any) {
    return this.canvas.toDataURL(type, quality);
  }

  download(filename: string = Date.now().toString()) {
    let ext = "png";
    if (filename.includes(".")) {
      const filenameSplit = filename.split(".");
      ext = filenameSplit[filenameSplit.length - 1].toLowerCase();
    }
    let mime = "image/png";
    if (ext === "jpg" || ext === "jpeg") {
      mime = "image/jpeg";
    }
    const base64 = this.toDataURL(mime);
    const aEl = document.createElement("a");
    aEl.href = base64;
    aEl.setAttribute("download", filename);
    aEl.click();
  }
}

export default Poster;
复制代码

 

使用示例:

复制代码
<script setup lang="ts">
import Poster from "@/utils/poster";
import QRCode from "qrcode";
import { onMounted, ref } from "vue";

defineOptions({
  name: "Poster",
});

// 获取本地图片
function getAssetImg(name: string) {
  return new URL(`../../assets/poster/${name}`, import.meta.url).href;
}

async function generatePoster() {
  const width = 750;
  const height = 1334;
  const poster = new Poster("#canvas", width, height);
  const bgImg = await poster.loadImage(getAssetImg("bg.jpeg"));
  // 生成二维码base64图片
  const qrcodeUrl = await QRCode.toDataURL(`https://www.baidu.com`);
  const qrcodeImg = await poster.loadImage(qrcodeUrl);
  // 开始绘制
  poster
    .drawImage(bgImg, 0, 0, width, height)
    .drawImage(qrcodeImg, width - 150, height - 150, 100, 100)
    .setFillStyle("#fff")
    .setFont("14px bold 黑体")
    .drawText("打开爱奇艺app,扫码领取积分,赢取豪华大礼,惊喜等着你~", 50, height - 100, 250, 20);

  return poster;
}

const posterObj = ref();
const isLoading = ref(true);

onMounted(async () => {
  posterObj.value = await generatePoster();
  isLoading.value = false;
});
</script>

<template>
  <div>
    <div v-if="isLoading">生成中。。。</div>
    <div v-show="!isLoading">
      <canvas id="canvas"></canvas>
      <div>
        <button type="button" @click="posterObj.download()">保存海报</button>
      </div>
    </div>
  </div>
</template>

<style lang="scss" scoped></style>
复制代码

 

 

posted @   白開水  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
点击右上角即可分享
微信分享提示