vue3 使用html2-canvas绘制海报

2,169 阅读1分钟

vue3 html2canvas:^1.0.0-rc.4(目前来说相对稳定的版本)

  • 直接上代码吧
<template>
  <van-popup
    v-model:show="visibles"
    position="bottom"
    :style="{
      'background-color': 'transparent',
      width: '100%',
      height: '100vh',
      'box-sizing': 'border-box',
    }"
    :overlay-style="{
      backgroundColor: 'rgba(0, 0, 0, 0.5)',
    }"
    ref="pup"
    @open="onOpen"
    @close="onClose"
    @opened="onOpened"
    @closed="onClosed"
    @click="onClosed"
    teleport="body"
  > 
  <!-- 海报主体内容 -->
    <div class="haipbox" ref="PosterHtml" v-show="!flag" @click.stop="() => {}">
      <img class="b1" src="@assets/images/readIndex/hpimg1.jpg" alt="" />
      <div class="b2">
        <div class="basePop2">
          <img class="bg6" src="@assets/images/readIndex/sb1.jpg" />
          <!-- 信件部分 -->
          <div class="mesBox">
            <img class="tag tag1" src="@assets/images/readIndex/tag1.png" />
            <div class="info">
              <div class="touxiangimg">
                <img class="atvar" :src="Items?.atvar" />
              </div>
              <div class="rbox">
                <div class="name">{{ Items.name }}</div>
                <div class="desc">{{ Items.desc }}</div>
              </div>
            </div>
            <!-- 信的内容 -->
            <div v-if="Items.message.length > 0" class="mes-content">
              <div v-for="(pm, px) in Items.message" class="mesp" :key="px">
                {{ pm }}
              </div>
            </div>
            <div v-else class="mes-content">
              <div class="mesp">{{ Items.famousword }}</div>
            </div>
          </div>

          <!-- 书籍部分 -->
          <div v-if="Items.bookList.length > 0" class="bookdesc-box">
            <img class="tag tag2" src="@assets/images/readIndex/tag2.png" />
            <van-image
              class="bookimg"
              alt=""
              :src="Items.bookList[Idx].bookiamge"
            >
              <template v-slot:loading>
                <van-loading type="spinner" size="20" />
              </template>
            </van-image>
            <div class="rt-box">
              <div class="bookname">{{ Items.bookList[Idx].bookname }}</div>
              <div class="author">
                <div class="tag3">作者</div>
                {{ Items.bookList[Idx].author }}
              </div>
              <div class="tags">推荐理由</div>
              <div class="descs">
                {{ Items.bookList[Idx].recomends }}
              </div>
            </div>
          </div>
        </div>
        <div class="hb-bottom">
          <img src="@assets/images/readIndex/bg7.jpg" />
        </div>
      </div>
    </div>
    
    <!-- 绘制的海报图片,绘制前先隐藏 -->
    <div v-show="flag" class="img-box" @click.stop="() => {}">
      <img class="postImg" :src="posterImg" alt="" />
    </div>
    <div class="closebtn" @click="onClosed"></div>
  </van-popup>
</template>

<script lang="ts">
import { defineComponent, nextTick, reactive, ref, toRefs, watch } from 'vue'
import html2canvas from 'html2canvas'   
export default defineComponent({
  props: {
    visible: {
      type: Boolean,
      required: true,
      default: false,
    }, 
  },
  setup(_props, { emit }) { 
    const visibles = ref(_props.visible) 
    const PosterHtml = ref(null) as any
    const pup = ref(null) as any 
    watch(
      () => _props.visible,
      n => {
        visibles.value = n
        if (!n) {
          data.posterImg = ''
          data.flag = false
        }
        if (n) {
          nextTick(() => {
            // 每次打开还原回顶部
            pup.value.popupRef.value.scrollTop = 0
          })
        }
      },
    )
    watch(
      () => _props.Item,
      n => {
        Items.value = n
      },
    )
    const onOpen = () => {
      emit('open')
    }
    const onClose = () => {
      emit('close')
    }
    const onOpened = () => {
      emit('opened')
    }
    const onClosed = () => {
      emit('closed')
    }
    const data = reactive({
      posterImg: '', // 最终生成的海报图片
      flag: false,
      posterUrl: '', // upc返回的
    })

    // 保存图片
    const saveImage = () => {}

    /**
     * 海报生成
     * */
    const createPoster = () => {
      // 生成海报
      const domObj = PosterHtml.value
      // 清晰度关键  像素比
      const dpi = DPR()
      console.log(dpi)
      html2canvas(domObj, {
        useCORS: true, // 是否允许图片跨域
        allowTaint: true,
        scrollY: 0,
        scrollX: 0,
        logging: false, // 日志
        width: domObj.offsetWidth, // 图片宽度
        height: domObj.offsetHeight,
        scale: dpi,
        backgroundColor: 'transparent',
      }).then(async canvas => {
        Toast('生成海报成功~')
        // 在微信里,可长按保存或转发
        data.posterImg = canvas.toDataURL('image/jpeg')
        // flag:生成后展示图片
        data.flag = true
        emit('showHp')
        if (
          isApp.value ||
          navigator.userAgent.indexOf('UCBrowser') > -1 ||
          navigator.userAgent.toLowerCase().toString().indexOf('qqbrowser') > -1
        ) {
          const hbData = data.posterImg.split(',')
          try {
            const params = {
              data: hbData[1],
              // keepSrc: 'yes',
              // readExif: 'no',
            }
            const res = (await startComentRepos.uploadQuickStream(
              params,
            )) as any
            const resJson = JSON.parse(res)
            console.log('文件', resJson)
            data.posterUrl = resJson.files[0].url
            console.log('posterUrl', data.posterUrl)
          } catch (error) {
            console.error(error)
          }
          // uc兼容
          if (
            navigator.userAgent.indexOf('UCBrowser') > -1 ||
            navigator.userAgent.toLowerCase().toString().indexOf('qqbrowser') >
              -1
          ) {
            data.posterImg = data.posterUrl
          }
        }
        // console.log('海报生成成功===>', data.posterImg)
      })
    }

    // 获取像素比
    const DPR = () => {
      // 获取设备dpi
      if (window.devicePixelRatio && window.devicePixelRatio > 1) {
        return window.devicePixelRatio * 2
      }
      // 直接返回高像素比
      return 8
    }

    const productHaibao = () => {
      nextTick(() => {
        Toast.loading({
          duration: 0,
          message: '海报生成中...',
          forbidClick: true,
        })
        setTimeout(() => {
          createPoster()
        }, 600)
      })
    }

    return {
      pup,
      productHaibao, 
      visibles, 
      DPR,
      onOpen,
      onClose,
      onOpened,
      onClosed,
      saveImage,
      PosterHtml,
      createPoster,
      ...toRefs(data),
    }
  },
})
</script>

  • 看看效果吧

image.png

image.png

  • 附带几个坑点修复方案
  1. html-canvas生成海报参考这个地址:blog.csdn.net/qq_41227106…
  2. 文字下划线bug:修改源码搜索underline===>Math.round(text.bounds.top + baseline) 变成 Math.round(text.bounds.top + baseline + 2)