canvas绘画海报之祝你生日快乐

目录

1. 绘制结果以及分析

2. canvas

3.  封装画图和画文字方法

4.  调用方法和传参

5.  提升清晰度

6. 完整代码 


前言:承接上文的html2canvas踩坑之后,实在是坑的需求要延期了哈,所以还是老老实实的用canvas来话吧。(心酸啊心酸)

1. 绘制结果以及分析

从上图可以看出,其实我们只要做两件事情,第一是用canvas画图,第二是用canvas画文字,调用这两个api就好了!

2. canvas

  好了,我们现在要开始了,首先,让我们把这个坐标系给建起来吧,

<canvas class="poster-canvas" type="2d" id="posterQuick"></canvas>

其次我们要拿到它,然后拿到它的渲染上下文

let canvas = document.getElementById('posterQuick');  // 拿到这个canvas
let ctx = this.canvas.getContext('2d');  // 拿到它的渲染上下文
// 给这个画布规定好它的宽高
canvas.width = 375;
canvas.height =756;

3.  封装画图和画文字方法

接下来我们就要开始画图了,让我们封装一下,把上下文,图片路径,图片起始坐标和宽高作为参数传入

drawImage(ctx, path, dx, dy, dw, dh) {
      return new Promise((resolve, reject) => {
        const image = new Image();
        image.src = path;
        image.onload = () => {
          ctx.drawImage(image, dx, dy, dw, dh);
          resolve();
        };
        image.onerror = () => {
          reject();
        };
      });
    },

同理,我们再封装一个绘画文字的方法,但是这个绘画文字的我们要加个options对象,在Options中传入对文字的一些设置,比如加粗,颜色,行高,字体大小,最大宽度等,还有首行缩进换行

fillText(ctx, text, dx, dy, options = {}) {
      let {
        color = '#000000',    // 字体颜色
        fontSize = 24,     // 字体大小
        lineHeight = undefined,  // 行高
        maxWidth = 375,  // 最大宽度
        maxLine = 0,  
        align = 'left',  // 文字对齐方式 
        bold = false,  // 是否加粗
        lastHidden = false,
        indent = 0,  // 缩进
      } = options;
      lineHeight = lineHeight || fontSize;
      ctx.fillStyle = color;  // 设置字体颜色
      ctx.textAlign = align; // 设置文字对齐方式
      // 是否加粗
      if (bold) {
        ctx.font = `bold ${fontSize}px sans-serifl`;
      } else {
        ctx.font = `${fontSize}px sans-serif`;
      }

      let words = text.split('');
      let line = '';
      let lines = [];
      for (let i = 0; i < words.length; i++) {
        let testLine = line + words[i] + '';
        let metrics = ctx.measureText(testLine);
        let testWidth = metrics.width;

        // 首行缩进处理
        if (!lines.length && indent) {
          testWidth = testWidth + indent;
        }

        // 换行处理
        if (testWidth > maxWidth) {
          lines.push(line);
          line = words[i] + ' ';
        } else {
          line = testLine;
        }
      }

      // 绘画文字
      lines.push(line);
      for (let j = 0; j < lines.length; j++) {
        if (j <= 0 && indent) {
          ctx.fillText(lines[j], dx + indent, dy + j * lineHeight);
        } else {
          ctx.fillText(lines[j], dx*this.dpr, dy + j * lineHeight);
        }
      }
    },

4.  调用方法和传参

async makePoster() {
      this.canvas = document.getElementById('posterQuick');
      let ctx = this.canvas.getContext('2d');
      this.imgHeight = this.$refs.urlInfo.offsetHeight || 756;  // 获取图片的渲染高度
      this.canvas.width =375;
      this.canvas.height = this.imgHeight;
      await this.drawImage(
        ctx,
        this.posterList[picIndex].imgurl,
        0,
        0,
        375,
        this.imgHeight,
      );
      await this.fillText(ctx, this.name, 43, 218, {
        fontSize: 16,
        color: '#222222',
        bold: true,
      });
      await this.fillText(ctx, this.postersConf.message, 43, 266, {
        fontSize: 13,
        color: '#333333',
        maxWidth: 290,
        lineHeight: 20,
        indent: 20,
        // maxLine: 5,
      });

      // this.empName是登录人姓名,此操作是在处理当名字大于三个字时,绘画名字的x坐标往前挪一挪,小于三个字时,绘画名字的x坐标往后挪一挪
      let startX = this.empName.length>3?262-(this.empName.length-3)*13: 262+(3-this.empName.length)*13;
      await this.fillText(ctx, this.empName, startX, 366, {
        fontSize: 13,
        color: '#333333',
        lineHeight: 18,
      });
      await this.fillText(ctx, '敬上', 306, 366, {
        fontSize: 13,
        color: '#333333',
        lineHeight: 18,
      });


     //  画完之后的canvas,转成了base64,可以丢到浏览器看绘画结果
     console.log('this.canvas.toDataURL()',this.canvas.toDataURL())
    },

5.  提升清晰度

  丢到浏览器应该是有点糊糊的吧,这里涉及到物理屏幕的一个分辨率,那我们应该怎么办呢,超级超级简单,我们把这个分辨率获取一下,如下

this.dpr = window.devicePixelRatio;

然后在我们指定画布大小,坐标,图片位置大小,文字字体等,可以说是几乎所有的动态参数我们都乘一下就可以了。

6. 完整代码 

<template>
  <div class="poster">
    <canvas
      class="poster-canvas"
      :style="{ height: imgHeight + 'px' }"
      type="2d"
      id="posterQuick"
    ></canvas>
    <div class="down_btn" @click="makePoster">下载海报</div>
    <div class="lucky-poster" ref="poster" id="poster1">
      <img
        :src="posterList[postersConf.picIndex].imgurl"
        ref="urlInfo"
        alt=""
      />
      <div class="luckiness">
        <div class="name">
          {{ name }}
        </div>
        <div class="text">{{ postersConf.message }}</div>
        <div class="company">{{ empName }}<span style="marginLeft: 6px">敬上</span></div>
      </div>
    </div>
  </div>
</template>
<script>
import { Toast } from 'vant';
export default {
  data() {
    return {
      postersConf: {
        applicantName: 'hahh',
        applicantSexDesc: '男',
        empName: 'yanan',
        picIndex: 0,
        message: 'hello'
      },
      imgHeight: 0,
      dpr: 1,
      canvas: null,
      posterList: [
        { id: 0, imgurl: require('../images/messageDetail/emptyBirth1.png') },
        { id: 1, imgurl: require('../images/messageDetail/emptyBirth2.png') },
        { id: 2, imgurl: require('../images/messageDetail/emptyBirth3.png') },
      ],
    };
  },
  created() {
    this.canvas = null;
    this.dpr = window.devicePixelRatio;
  },
  mounted() {
  },
  computed: {
    name() {
      return `尊敬的${this.postersConf.applicantName}${
        this.postersConf.applicantSexDesc == '男' ? '先生' : '女士'
      }`;
    },
    empName() {
      return `${this.postersConf.empName}`;
    },
  },
  mounted() {},
  methods: {
    drawImage(ctx, path, dx, dy, dw, dh) {
      return new Promise((resolve, reject) => {
        const image = new Image();
        image.src = path;
        image.onload = () => {
          ctx.drawImage(image, dx*this.dpr, dy*this.dpr, dw*this.dpr, dh*this.dpr);
          resolve();
        };
        image.onerror = () => {
          reject();
        };
      });
    },
    fillText(ctx, text, dx, dy, options = {}) {
      let {
        color = '#000000',
        fontSize = 24,
        lineHeight = undefined,
        maxWidth = 750,
        maxLine = 0,
        align = 'left',
        bold = false,
        lastHidden = false,
        indent = 0,
      } = options;
      lineHeight = lineHeight || fontSize;
      ctx.fillStyle = color;
      ctx.textAlign = align;
      ctx.textBaseline = 'top';

      if (bold) {
        ctx.font = `bold ${fontSize*this.dpr}px sans-serifl`;
      } else {
        ctx.font = `${fontSize*this.dpr}px sans-serif`;
      }

      let words = text.split('');
      let line = '';
      let lines = [];
      for (let i = 0; i < words.length; i++) {
        let testLine = line + words[i] + '';
        let metrics = ctx.measureText(testLine);
        let testWidth = metrics.width;
        if (!lines.length && indent) {
          testWidth = testWidth + indent*this.dpr;
        }
        if (testWidth > maxWidth*this.dpr) {
          lines.push(line);
          line = words[i] + ' ';
        } else {
          line = testLine;
        }
      }
      lines.push(line);
      for (let j = 0; j < lines.length; j++) {
        if (j <= 0 && indent) {
          ctx.fillText(lines[j], (dx + indent)*this.dpr, (dy + j * lineHeight)*this.dpr);
        } else {
          ctx.fillText(lines[j], dx*this.dpr, (dy + j * lineHeight)*this.dpr);
        }
      }
    },
    async makePoster() {
      let picIndex = this.postersConf.picIndex;
      this.canvas = document.getElementById('posterQuick');
      let ctx = this.canvas.getContext('2d');
      console.log(this.$refs.urlInfo.offsetHeight, 'ctx', ctx);
      this.imgHeight = this.$refs.urlInfo.offsetHeight || 812;
      this.canvas.width = Math.round(375*this.dpr);
      this.canvas.height = Math.round(this.imgHeight*this.dpr);
      await this.drawImage(
        ctx,
        this.posterList[picIndex].imgurl,
        0,
        0,
        375,
        this.imgHeight,
      );
      await this.fillText(ctx, this.name, 43, 218, {
        fontSize: 16,
        color: '#222222',
        bold: true,
      });
      await this.fillText(ctx, this.postersConf.message, 43, 266, {
        fontSize: 13,
        color: '#333333',
        maxWidth: 290,
        lineHeight: 20,
        indent: 20,
        // maxLine: 5,
      });
      let startX = this.empName.length>3?262-(this.empName.length-3)*13: 262+(3-this.empName.length)*13;
      await this.fillText(ctx, this.empName, startX, 366, {
        fontSize: 13,
        color: '#333333',
        lineHeight: 18,
      });
      await this.fillText(ctx, '敬上', 306, 366, {
        fontSize: 13,
        color: '#333333',
        lineHeight: 18,
      });
         let link =  this.canvas.toDataURL();

    },
  },
};
</script>
<style lang="scss" scoped>
.poster-canvas {
  width: 375px;
  position: absolute;
  right: 9999rpx;
  top: 9999rpx;
  z-index: -10;
}
.down_btn {
  position: absolute;
  width: 211px;
  top: 605px;
  left: 82px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #ff7b28;
  border-radius: 22px;
  border: 3px solid #ffffff;
  font-family: PingFangSC, PingFang SC;
  font-weight: 500;
  font-size: 14px;
  color: #ffffff;
  line-height: 44px;
  z-index: 2;
}
.lucky-poster {
  font-family: PingFangSC-Medium, PingFang SC;
  font-weight: bold;
  color: #333333;
  position: relative;
  img {
    display: block;
    width: 100%;
  }
  .luckiness {
    position: absolute;
    color: rgb(227, 220, 220);
    top: 208px;
    left: 42px;
    right: 42px;
    font-family: PingFangSC, PingFang SC;
    font-weight: 400;
    font-size: 13px;
    color: #333333;
    line-height: 22px;
    .name {
      font-weight: 600;
      font-size: 16px;
      color: #222222;
    }
    .text {
      text-indent: 26px;
      width: 290px;
      height: 105px;
      margin-top: 16px;
      text-align: justify;
      font-style: normal;
      line-height: 22px;
    }
    .company {
      line-height: 18px;
      text-align: right;
    }
  }
}
</style>

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值