Taro分享海报,canvas生成图片

一:业务代码

1:小程序(微信/支付宝小程序)版

步骤详解:1:使用canvas,画出所需;2:根据canvas生成url,绑定到image上

import { useEffect } from 'react'
import { useSelector } from 'react-redux'
import Taro from '@tarojs/taro'
import { View, Text, Image, Canvas } from '@tarojs/components'
import { useAsyncCallback } from '@/hooks'
import { classNames, authSetting, showToast, isWeixin, isAlipay } from '@/utils'
import GoodsDetailPoster from './dw-goodsdetail'
import './index.scss'

const initialState = {
  poster: null,
  pxWidth: 200,
  pxHeight: 200,
  factor: 1,
  eleId: 'poster-canvas',
  ctx: null
}

function SpPoster(props) {
  const { info, type, onClose = () => { } } = props
  const { userInfo } = useSelector((state) => state.user)
  const { userInfo: guideInfo } = useSelector((state) => state.guide)
  const [state, setState] = useAsyncCallback(initialState)

  const { poster, pxWidth, pxHeight, eleId, ctx } = state

  useEffect(() => {
    handleCreatePoster()
  }, [])

  /**
   * @description rpx => px 基础方法
   * @param { number } rpx - 需要转换的数值
   * @param { boolean} int - 是否为 int
   * @param { number } [factor] - 转化因子
   * @returns { number }
   */
  const toPx = (rpx, int, factor = 1) => {
    if (int) {
      return parseInt(rpx * factor)
    }
    return rpx * factor
  }
  /**
   * @description px => rpx
   * @param { number } px - 需要转换的数值
   * @param { boolean} int - 是否为 int
   * @param { number } [factor] - 转化因子
   * @returns { number }
   */
  const toRpx = (px, int, factor = 1) => {
    if (int) {
      return parseInt(px / factor)
    }
    return px / factor
  }

  const handleCreatePoster = async () => {
    Taro.showLoading({
      title: '海报生成中...'
    })
    const ctx = Taro.createCanvasContext(eleId, Taro.getCurrentInstance().page)
    let canvasObj
    switch (type) {
      case 'goodsDetial':
        canvasObj = new GoodsDetailPoster({
          ctx,
          info,
          userInfo,
          toPx,
          toRpx
        })
        break
      default:
        break
    }
    const { canvasWidth, canvasHeight } = canvasObj.getCanvasSize()
    setState(
      (draft) => {
        draft.pxWidth = canvasWidth
        draft.pxHeight = canvasHeight
        draft.ctx = ctx
      },
      async (_state) => {
        await canvasObj.drawPoster()
        const poster = await getPoster(_state)
        console.log('handleCreatePoster:getPoster', poster)
        Taro.hideLoading()
        setState((draft) => {
          draft.poster = poster
        })
      }
    )
  }

  const getPoster = ({ ctx, pxWidth, pxHeight, eleId }) => {
    return new Promise((resolve, reject) => {
      try {
        ctx.draw(false, async () => {
          if (isWeixin) {
            const { tempFilePath } = await Taro.canvasToTempFilePath(
              {
                x: 0,
                y: 0,
                width: pxWidth,
                height: pxHeight,
                canvasId: eleId
              },
              Taro.getCurrentInstance().page
            )
            resolve(tempFilePath)
          } else if(isAlipay) {
            my.canvasToTempFilePath({
              canvasId: eleId,
              success: ({ apFilePath }) => {
                console.log('success', apFilePath);
                resolve(apFilePath)
              }
            });
          }
        })
      } catch (e) {
        console.error(e)
        reject(e)
      }
    })
  }

  const saveToAlbum = () => {
    authSetting(
      'writePhotosAlbum',
      () => {
        savePoster()
      },
      () => {
        showToast('请设置允许保存到相册')
      }
    )
  }

  const savePoster = () => {
    Taro.saveImageToPhotosAlbum({
      filePath: poster,
      success: (res) => {
        showToast('保存成功')
      },
      fail: (res) => {
        console.log(res)
        showToast('保存失败')
      }
    })
  }

  return (
    <View className={classNames('sp-poster')}>
      <View className='share-panel__overlay'></View>
      {poster && (
        <View className='share-panel__poster'>
          <View className='poster-container'>
            <Image className='poster' src={poster} mode='scaleToFill' />
          </View>
          <View className='poster-ft'>
            <Text className='iconfont icon-guanbi' onClick={onClose}></Text>
            <View className='poster-save' onClick={saveToAlbum}>
              <Text className='iconfont icon-download'></Text>
              保存图片
            </View>
          </View>
        </View>
      )}
      <Canvas
        className='canvasbox'
        canvasId='poster-canvas'
        id='poster-canvas'
        width={pxWidth}
        height={pxHeight}
        style={`width:${pxWidth}px; height:${pxHeight}px;`}
      />
    </View>
  )
}

export default SpPoster

import Taro, { Component } from '@tarojs/taro'
import api from '@/api'
import { getExtConfigData, isAlipay, } from '@/utils'
import { drawText, drawImage, drawBlock } from './helper'


//计算canvas画布尺寸
let canvasWidth = 600
let canvasHeight = 960


class GoodsDetailPoster {
  constructor(props) {
    const { ctx, info, userInfo, toPx, toRpx, canvas } = props

    this.ctx = ctx
    this.info = info
    this.userInfo = userInfo
    this.toPx = toPx
    this.toRpx = toRpx
    // ctx.scale(dpr, dpr)
    // // alipay2.0 兼容
    // this.canvas = canvas
  }

  getCanvasSize() {
    return {
      canvasWidth: canvasWidth,
      canvasHeight: canvasHeight
    }
  }

  async drawPoster() {
    const host = process.env.APP_BASE_URL.replace('/api/h5app/wxapp', '')
    const { appid, company_id } = getExtConfigData()
    const { itemId, imgs, price } = this.info
    const { user_id, avatar } = this.userInfo
    let wxappCode
    // TODO 获取微信二维码的接口,需要换alipay  https://ecshopx1.shopex123.com/api/h5app/alipaymini/qrcode.png?company_id=1&page=page/index
    // const res = await api.alipay.alipay_qrcode(`page=${`pages/item/espier-detail`}&appid=${appid}&company_id=${company_id}&id=${itemId}&uid=${user_id}`)
    const res = await Taro.request({
      url: `${host}/api/h5app/alipaymini/qrcode.png?page=${`pages/item/espier-detail`}&appid=${appid}&company_id=${company_id}&id=${itemId}&uid=${user_id}`, //仅为示例,并非真实的接口地址
      header: {
        'content-type': 'application/json' // 默认值
      },
    })
    wxappCode = res.data.data.qr_code_url

    const pic = imgs[0].replace('http:', 'https:')
    // 商品图片
    this.goodsImg = await Taro.getImageInfo({ src: pic })
    // 太阳码
    this.codeImg = await Taro.getImageInfo({ src: wxappCode })
    // 头像
    const _avatar = avatar || `${process.env.APP_IMAGE_CDN}/user_icon.png`
    this.avatar = await Taro.getImageInfo({ src: _avatar })

    const drawOptions = {
      ctx: this.ctx,
      toPx: this.toPx,
      toRpx: this.toRpx
    }

    this.drawOptions = drawOptions
    const { username } = this.userInfo
    drawBlock(
      {
        x: 0,
        y: 0,
        width: canvasWidth,
        height: canvasHeight,
        backgroundColor: '#fff'
      },
      drawOptions
    )
    console.log('海报商品图:', this.goodsImg)
    console.log('太阳码:', this.codeImg)
    console.log('头像:', this.avatar, avatar)
    // 海报商品图
    drawImage(
      {
        imgPath: this.goodsImg.path,
        x: 0,
        y: 0,
        w: canvasWidth,
        h: canvasWidth,
        sx: 0,
        sy: 0,
        sw: this.goodsImg.width,
        sh: this.goodsImg.height
      },
      drawOptions
    )
    // 头像背景
    drawBlock(
      {
        x: 24,
        y: 624,
        width: 312,
        height: 40 * 2,
        backgroundColor: '#efefef',
        borderRadius: 80
      },
      drawOptions
    )
    // 头像
    drawImage(
      {
        imgPath: this.avatar.path,
        x: 24,
        y: 624,
        w: 80,
        h: 80,
        sx: 0,
        sy: 0,
        sw: this.avatar.width,
        sh: this.avatar.height,
        borderRadius: 80
      },
      drawOptions,
    )
    // 姓名
    drawText(
      {
        x: 112,
        y: 656,
        fontSize: 24,
        color: '#000',
        text: username
      },
      drawOptions
    )
    //
    drawText(
      {
        x: 112,
        y: 688,
        fontSize: 22,
        color: '#999',
        text: '推荐一个好物给你'
      },
      drawOptions
    )
    // 商品金额
    const initPrice = price.toFixed(2).split('.')[0]
    const floatPrice = `.${price.toFixed(2).split('.')[1]}`
    drawText(
      {
        x: 24,
        y: 815,
        color: '#222',
        text: [
          {
            text: '¥',
            fontSize: 28,
            color: '#222'
          },
          {
            text: initPrice,
            fontSize: 46,
            color: '#222'
          },
          {
            text: floatPrice,
            fontSize: 32,
            color: '#222'
          }
        ]
      },
      drawOptions
    )
    // 商品名称
    drawText(
      {
        x: 24,
        y: 887,
        fontSize: 24,
        width: 312,
        color: '#666',
        text: this.info.itemName,
        lineNum: 2
      },
      drawOptions
    )
    // 太阳码
    drawImage(
      {
        imgPath: this.codeImg.path,
        x: 416,
        y: 742,
        w: 160,
        h: 195,
        sx: 0,
        sy: 0,
        sw: this.codeImg.width,
        sh: this.codeImg.height
      },
      drawOptions,
    )
  }
}

export default GoodsDetailPoster

2:h5

import { useEffect, useRef, useDidShow } from 'react'
import { SpImage } from '@/components'
import Taro, { useReady } from '@tarojs/taro'
import { Image, Canvas, Text, View } from '@tarojs/components'
import { useImmer } from 'use-immer'
import { drawText, drawImage, drawBlock, drawLine } from './helper'
import './comgm.scss'

//h5图片
const imgurl = new window.Image()
imgurl.setAttribute('crossOrigin', 'anonymous')
imgurl.src =
  'https://ecshopx1.yuanyuanke.cn/image/34/2023/08/05/536550ac2434d3a8f0f883018ea4d302aKxnpYwnUYFgTrOPhf66Q5CA1vA4xoXd'

const initState = {
  logoImg: ''
}
function Test(props) {
  const canvasWidth = 610
  const canvasHeight = 1085
  const { show, handleHidden = () => {} } = props
  const [state, setState] = useImmer(initState)
  const {logoImg} = state
  useEffect(() => {
    if (show) getDraw()
    return () => {
      setState((draft) => {
        draft.logoImg = null
      })
    }
  }, [show])

  const canvasRef = useRef(null)

  /**
   * @description rpx => px 基础方法
   * @param { number } rpx - 需要转换的数值
   * @param { boolean} int - 是否为 int
   * @param { number } [factor] - 转化因子
   * @returns { number }
   */
  const toPx = (rpx, int, factor = 1) => {
    if (int) {
      return parseInt(rpx * factor)
    }
    return rpx * factor
  }
  /**
   * @description px => rpx
   * @param { number } px - 需要转换的数值
   * @param { boolean} int - 是否为 int
   * @param { number } [factor] - 转化因子
   * @returns { number }
   */
  const toRpx = (px, int, factor = 1) => {
    if (int) {
      return parseInt(px / factor)
    }
    return px / factor
  }

  const getDraw = async () => {
    const ctx = Taro.createCanvasContext('order1')
    const dom = await document.getElementsByTagName('canvas')[0]
    dom.width = canvasWidth
    dom.height = canvasHeight
    const drawOptions = {
      ctx: ctx,
      toPx: toPx,
      toRpx: toRpx
    }
    drawBlock(
      {
        x: 0,
        y: 0,
        width: canvasWidth,
        height: canvasHeight,
        backgroundColor: '#FFFFFF',
        borderRadius: 20
      },
      drawOptions
    )
    drawBlock(
      {
        x: 32,
        y: 16,
        width: 546,
        height: 120,
        backgroundColor: '#f5f5f5',
        borderRadius: 0
      },
      drawOptions
    )
    drawText(
      {
        x: 124,
        width: 600,
        y: 80,
        fontSize: 32,
        color: '#000',
        text: 'name:'
      },
      drawOptions
    )

    drawText(
      {
        x: 250,
        width: 120,
        y: 80,
        fontSize: 32,
        color: '#000',
        text: '小明'
      },
      drawOptions
    )
    drawLine(
      {
        startX: 32,
        startY: 140,
        endX: 546,
        endY: 140,
        color: '#ddd',
        width: 1
      },
      drawOptions
    )
    drawImage(
      {
        imgPath: imgurl,
        x: 116,
        y: 325,
        w: 380,
        h: 380,
        sx: 0,
        sy: 0,
        sw: imgurl.width,
        sh: imgurl.height,
        hasWidth: true
      },
      drawOptions
    )
    ctx.draw()
    setTimeout(() => {
      handleDown()
    })
  }
  const handleDown = async () => {
    const canvas = await document.getElementsByTagName('canvas')[0]
    const dataURL = canvas.toDataURL('image/png')
    setState((draft) => {
      draft.logoImg = dataURL
    })
  }

  return (
    <View
      className='order-poster'
      style={show ? { top: '0px', left: '0px' } : {}}
      onClick={handleHidden}
    >
      {logoImg && (
        <SpImage onClick={e=>e.stopPropagation()} className='order-canvas' src={logoImg} height={canvasHeight} width={canvasWidth} />
      )}
      {!logoImg && (
      <Canvas
        canvasId='order1'
        className='order-canvas'
        style={{
          width: `${Taro.pxTransform(canvasWidth)}`,
          height: `${Taro.pxTransform(canvasHeight)}`
        }}
        ref={canvasRef}
      ></Canvas>
      )}
    </View>
  )
}
export default Test

二:utils function

直接使用即可

/**
 * @description 绘制圆角矩形
 * @param { object } drawData - 绘制数据
 * @param { number } drawData.x - 左上角x坐标
 * @param { number } drawData.y - 左上角y坐标
 * @param { number } drawData.w - 矩形的宽
 * @param { number } drawData.h - 矩形的高
 * @param { number } drawData.r - 圆角半径
 *
 * @param { object } drawOptions - 绘制对象
 * @param { object } drawOptions.ctx - ctx对象
 * @param { function } drawOptions.toPx - toPx方法
 * @param { function } drawOptions.toRpx - toRpx方法
 */
export function _drawRadiusRect(drawData, drawOptions) {
  const { x, y, w, h, r } = drawData
  const {
    ctx,
    toPx
    // toRpx,
  } = drawOptions
  const br = r / 2
  ctx.beginPath()
  ctx.moveTo(toPx(x + br), toPx(y)) // 移动到左上角的点
  ctx.lineTo(toPx(x + w - br), toPx(y))
  ctx.arc(toPx(x + w - br), toPx(y + br), toPx(br), 2 * Math.PI * (3 / 4), 2 * Math.PI * (4 / 4))
  ctx.lineTo(toPx(x + w), toPx(y + h - br))
  ctx.arc(toPx(x + w - br), toPx(y + h - br), toPx(br), 0, 2 * Math.PI * (1 / 4))
  ctx.lineTo(toPx(x + br), toPx(y + h))
  ctx.arc(toPx(x + br), toPx(y + h - br), toPx(br), 2 * Math.PI * (1 / 4), 2 * Math.PI * (2 / 4))
  ctx.lineTo(toPx(x), toPx(y + br))
  ctx.arc(toPx(x + br), toPx(y + br), toPx(br), 2 * Math.PI * (2 / 4), 2 * Math.PI * (3 / 4))
}

/**
 * @description 计算文本长度
 * @param { Array | Object } text 数组 或者 对象
 *
 * @param { object } drawOptions - 绘制对象
 * @param { object } drawOptions.ctx - ctx对象
 * @param { function } drawOptions.toPx - toPx方法
 * @param { function } drawOptions.toRpx - toRpx方法
 */
export function _getTextWidth(text, drawOptions) {
  const { ctx, toPx, toRpx } = drawOptions
  let texts = []
  if (Object.prototype.toString.call(text) === '[object Object]') {
    texts.push(text)
  } else {
    texts = text
  }
  let width = 0
  // eslint-disable-next-line no-shadow
  texts.forEach(({ fontSize, text, marginLeft = 0, marginRight = 0 }) => {
    ctx.setFontSize(toPx(fontSize))
    width += ctx.measureText(text).width + marginLeft + marginRight
  })
  return toRpx(width)
}

/**
 * @description 渲染一段文字
 * @param { object } drawData - 绘制数据
 * @param { number } drawData.x - x坐标 rpx
 * @param { number } drawData.y - y坐标 rpx
 * @param { number } drawData.fontSize - 文字大小 rpx
 * @param { number } [drawData.color] - 颜色
 * @param { string } [drawData.baseLine] - 基线对齐方式 top| middle|bottom
 * @param { string } [drawData.textAlign='left'] - 对齐方式 left|center|right
 * @param { string } drawData.text - 当Object类型时,参数为 text 字段的参数,marginLeft、marginRight这两个字段可用
 * @param { number } [drawData.opacity=1] - 1为不透明,0为透明
 * @param { string } [drawData.textDecoration='none']
 * @param { number } [drawData.width] - 文字宽度 没有指定为画布宽度
 * @param { number } [drawData.lineNum=1] - 根据宽度换行,最多的行数
 * @param { number } [drawData.lineHeight=0] - 行高
 * @param { string } [drawData.fontWeight='normal'] - 'bold' 加粗字体,目前小程序不支持 100 - 900 加粗
 * @param { string } [drawData.fontStyle='normal'] - 'italic' 倾斜字体
 * @param { string } [drawData.fontFamily="sans-serif"] - 小程序默认字体为 'sans-serif', 请输入小程序支持的字体
 *
 * @param { object } drawOptions - 绘制对象
 * @param { object } drawOptions.ctx - ctx对象
 * @param { function } drawOptions.toPx - toPx方法
 * @param { function } drawOptions.toRpx - toRpx方法
 */
export function _drawSingleText(drawData, drawOptions) {
  const {
    x,
    y,
    fontSize,
    color,
    baseLine,
    textAlign = 'left',
    text,
    opacity = 1,
    textDecoration = 'none',
    width,
    lineNum = 1,
    lineHeight = 0,
    fontWeight = 'normal',
    fontStyle = 'normal',
    fontFamily = 'sans-serif'
  } = drawData
  // console.log(drawData)
  const { ctx, toPx, toRpx } = drawOptions
  ctx.save()
  ctx.beginPath()
  ctx.font = fontStyle + ' ' + fontWeight + ' ' + toPx(fontSize, true) + 'px ' + fontFamily
  ctx.setGlobalAlpha(opacity)
  // ctx.setFontSize(toPx(fontSize));
  ctx.setFillStyle(color)
  ctx.setTextBaseline(baseLine)
  ctx.setTextAlign(textAlign)
  let textWidth = toRpx(ctx.measureText(text).width)
  const textArr = []
  if (textWidth > width) {
    // 文本宽度 大于 渲染宽度
    let fillText = ''
    let line = 1
    for (let i = 0; i <= text.length - 1; i++) {
      // 将文字转为数组,一行文字一个元素
      fillText = fillText + text[i]
      if (toRpx(ctx.measureText(fillText).width) >= width) {
        if (line === lineNum) {
          if (i !== text.length - 1) {
            fillText = fillText.substring(0, fillText.length - 1) + '...'
          }
        }
        if (line <= lineNum) {
          textArr.push(fillText)
        }
        fillText = ''
        line++
      } else {
        if (line <= lineNum) {
          if (i === text.length - 1) {
            textArr.push(fillText)
          }
        }
      }
    }
    textWidth = width
  } else {
    textArr.push(text)
  }
  textArr.forEach((item, index) => {
    ctx.fillText(item, toPx(x), toPx(y + (lineHeight || fontSize) * index))
  })
  ctx.restore()
  // textDecoration
  if (textDecoration !== 'none') {
    let lineY = y
    if (textDecoration === 'line-through') {
      // 目前只支持贯穿线
      lineY = y - fontSize * 0.4
    }
    ctx.save()
    ctx.moveTo(toPx(x), toPx(lineY))
    ctx.lineTo(toPx(x) + toPx(textWidth), toPx(lineY))
    ctx.setStrokeStyle(color)
    ctx.stroke()
    ctx.restore()
  }
  return textWidth
}

/**
 * 渲染文字
 * @param { object } params - 绘制数据
 * @param { number } params.x - x坐标 rpx
 * @param { number } params.y - y坐标 rpx
 * @param { number } params.fontSize - 文字大小 rpx
 * @param { number } [params.color] - 颜色
 * @param { string } [params.baseLine] - 基线对齐方式 top| middle|bottom
 * @param { string } [params.textAlign='left'] - 对齐方式 left|center|right
 * @param { string } params.text - 当Object类型时,参数为 text 字段的参数,marginLeft、marginRight这两个字段可用
 * @param { number } [params.opacity=1] - 1为不透明,0为透明
 * @param { string } [params.textDecoration='none']
 * @param { number } [params.width] - 文字宽度 没有指定为画布宽度
 * @param { number } [params.lineNum=1] - 根据宽度换行,最多的行数
 * @param { number } [params.lineHeight=0] - 行高
 * @param { string } [params.fontWeight='normal'] - 'bold' 加粗字体,目前小程序不支持 100 - 900 加粗
 * @param { string } [params.fontStyle='normal'] - 'italic' 倾斜字体
 * @param { string } [params.fontFamily="sans-serif"] - 小程序默认字体为 'sans-serif', 请输入小程序支持的字体
 *
 * @param { object } drawOptions - 绘制对象
 * @param { object } drawOptions.ctx - ctx对象
 * @param { function } drawOptions.toPx - toPx方法
 * @param { function } drawOptions.toRpx - toRpx方法
 */
export function drawText(params, drawOptions) {
  // const { ctx, toPx, toRpx } = drawOptions;
  const {
    x,
    y,
    text,
    baseLine
    // fontSize,
    // color,
    // textAlign,
    // opacity = 1,
    // width,
    // lineNum,
    // lineHeight
  } = params
  if (Object.prototype.toString.call(text) === '[object Array]') {
    let preText = { x, y, baseLine }
    text.forEach((item) => {
      preText.x += item.marginLeft || 0
      const textWidth = _drawSingleText(
        Object.assign(item, {
          ...preText
        }),
        drawOptions
      )
      preText.x += textWidth + (item.marginRight || 0) // 下一段字的 x 轴为上一段字 x + 上一段字宽度
    })
  } else {
    _drawSingleText(params, drawOptions)
  }
}

/**
 * @description 渲染图片
 * @param { object } data
 * @param { number } x - 图像的左上角在目标 canvas 上 x 轴的位置
 * @param { number } y - 图像的左上角在目标 canvas 上 y 轴的位置
 * @param { number } w - 在目标画布上绘制图像的宽度,允许对绘制的图像进行缩放
 * @param { number } h - 在目标画布上绘制图像的高度,允许对绘制的图像进行缩放
 * @param { number } sx - 源图像的矩形选择框的左上角 x 坐标
 * @param { number } sy - 源图像的矩形选择框的左上角 y 坐标
 * @param { number } sw - 源图像的矩形选择框的宽度
 * @param { number } sh - 源图像的矩形选择框的高度
 * @param { number } [borderRadius=0] - 圆角
 * @param { number } [borderWidth=0] - 边框
 *
 * @param { object } drawOptions - 绘制对象
 * @param { object } drawOptions.ctx - ctx对象
 * @param { function } drawOptions.toPx - toPx方法
 * @param { function } drawOptions.toRpx - toRpx方法
 */
export function drawImage(data, drawOptions) {
  const { ctx, toPx } = drawOptions
  let {
    imgPath,
    x,
    y,
    w,
    h,
    sx,
    sy,
    sw,
    sh,
    borderRadius = 0,
    borderWidth = 0,
    borderColor,
  } = data
  ctx.save()
  if (borderRadius > 0) {
    let drawData = {
      x,
      y,
      w,
      h,
      r: borderRadius
    }
    _drawRadiusRect(drawData, drawOptions)
    ctx.strokeStyle = 'rgba(255,255,255,0)'
    ctx.stroke()
    ctx.clip()
    ctx.drawImage(
      imgPath,
      toPx(sx),
      toPx(sy),
      toPx(sw),
      toPx(sh),
      toPx(x),
      toPx(y),
      toPx(w),
      toPx(h)
    )
    if (borderWidth > 0) {
      ctx.setStrokeStyle(borderColor)
      ctx.setLineWidth(toPx(borderWidth))
      ctx.stroke()
    }
  } else {
    ctx.drawImage(
      imgPath,
      toPx(sx),
      toPx(sy),
      toPx(sw),
      toPx(sh),
      toPx(x),
      toPx(y),
      toPx(w),
      toPx(h)
    )
  }
  ctx.restore()
}

/**
 * @description 渲染线
 * @param  { number } startX - 起始坐标
 * @param  { number } startY - 起始坐标
 * @param  { number } endX - 终结坐标
 * @param  { number } endY - 终结坐标
 * @param  { number } width - 线的宽度
 * @param  { string } [color] - 线的颜色
 *
 * @param { object } drawOptions - 绘制对象
 * @param { object } drawOptions.ctx - ctx对象
 * @param { function } drawOptions.toPx - toPx方法
 * @param { function } drawOptions.toRpx - toRpx方法
 */
export function drawLine(drawData, drawOptions) {
  const { startX, startY, endX, endY, color, width } = drawData
  const { ctx, toPx } = drawOptions
  ctx.save()
  ctx.beginPath()
  ctx.setStrokeStyle(color)
  ctx.setLineWidth(toPx(width))
  ctx.moveTo(toPx(startX), toPx(startY))
  ctx.lineTo(toPx(endX), toPx(endY))
  ctx.stroke()
  ctx.closePath()
  ctx.restore()
}

/**
 * @description 渲染块
 * @param  { number } x - x坐标
 * @param  { number } y - y坐标
 * @param  { number } height -高
 * @param  { string|object } [text] - 块里面可以填充文字,参考texts字段
 * @param  { number } [width=0] - 宽 如果内部有文字,由文字宽度和内边距决定
 * @param  { number } [paddingLeft=0] - 内左边距
 * @param  { number } [paddingRight=0] - 内右边距
 * @param  { number } [borderWidth] - 边框宽度
 * @param  { string } [backgroundColor] - 背景颜色
 * @param  { string } [borderColor] - 边框颜色
 * @param  { number } [borderRadius=0] - 圆角
 * @param  { number } [opacity=1] - 透明度
 *
 * @param { object } drawOptions - 绘制对象
 * @param { object } drawOptions.ctx - ctx对象
 * @param { function } drawOptions.toPx - toPx方法
 * @param { function } drawOptions.toRpx - toRpx方法
 */
export function drawBlock(
  {
    text,
    width = 0,
    height,
    x,
    y,
    paddingLeft = 0,
    paddingRight = 0,
    borderWidth,
    backgroundColor,
    borderColor,
    borderRadius = 0,
    opacity = 1
  },
  drawOptions
) {
  const { ctx, toPx } = drawOptions
  // 判断是否块内有文字
  let blockWidth = 0 // 块的宽度
  let textX = 0
  let textY = 0
  if (typeof text !== 'undefined') {
    // 如果有文字并且块的宽度小于文字宽度,块的宽度为 文字的宽度 + 内边距
    const textWidth = _getTextWidth(typeof text.text === 'string' ? text : text.text, drawOptions)
    blockWidth = textWidth > width ? textWidth : width
    blockWidth += paddingLeft + paddingLeft

    const {
      textAlign = 'left'
      // text: textCon,
    } = text
    textY = height / 2 + y // 文字的y轴坐标在块中线
    if (textAlign === 'left') {
      // 如果是右对齐,那x轴在块的最左边
      textX = x + paddingLeft
    } else if (textAlign === 'center') {
      textX = blockWidth / 2 + x
    } else {
      textX = x + blockWidth - paddingRight
    }
  } else {
    blockWidth = width
  }

  if (backgroundColor) {
    // 画面
    ctx.save()
    ctx.setGlobalAlpha(opacity)
    ctx.setFillStyle(backgroundColor)
    if (borderRadius > 0) {
      // 画圆角矩形
      let drawData = {
        x,
        y,
        w: blockWidth,
        h: height,
        r: borderRadius
      }
      _drawRadiusRect(drawData, drawOptions)
      ctx.fill()
    } else {
      ctx.fillRect(toPx(x), toPx(y), toPx(blockWidth), toPx(height))
    }
    ctx.restore()
  }
  if (borderWidth) {
    // 画线
    ctx.save()
    ctx.setGlobalAlpha(opacity)
    ctx.setStrokeStyle(borderColor)
    ctx.setLineWidth(toPx(borderWidth))
    if (borderRadius > 0) {
      // 画圆角矩形边框
      let drawData = {
        x,
        y,
        w: blockWidth,
        h: height,
        r: borderRadius
      }
      _drawRadiusRect(drawData, drawOptions)
      ctx.stroke()
    } else {
      ctx.strokeRect(toPx(x), toPx(y), toPx(blockWidth), toPx(height))
    }
    ctx.restore()
  }

  if (text) {
    drawText(Object.assign(text, { x: textX, y: textY }), drawOptions)
  }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
你可以使用 Taro 和 Vue 来生成图片。以下是一个简单的示例: 1. 首先,确保你已经在项目中安装了 Taro 和 Vue。 2. 创建一个新的 Vue 文件,比如 `ImageGenerator.vue`。 3. 在 `ImageGenerator.vue` 文件中,添加一个用于生成图片的方法,比如 `generateImage()`。你可以使用 HTML5 的 `canvas` 元素来生成图片。 ```vue <template> <div> <button @click="generateImage">生成图片</button> <canvas ref="canvas"></canvas> </div> </template> <script> export default { methods: { generateImage() { const canvas = this.$refs.canvas; const context = canvas.getContext('2d'); // 在 canvas 上绘制你想要的内容 context.fillStyle = 'red'; context.fillRect(0, 0, 200, 200); // 使用 toDataURL 将 canvas 转换为 base64 格式的图片数据 const imageData = canvas.toDataURL(); // 创建一个新的图片元素 const image = new Image(); // 设置图片的 src 为生成图片数据 image.src = imageData; // 将图片插入到页面中 document.body.appendChild(image); } } }; </script> ``` 4. 在需要使用该组件的地方引入并使用它。 ```vue <template> <div> <image-generator></image-generator> </div> </template> <script> import ImageGenerator from './ImageGenerator.vue'; export default { components: { ImageGenerator } }; </script> ``` 这样,当你点击 "生成图片" 按钮时,将会在页面中生成一个红色的矩形图片。你可以根据需要修改 `generateImage()` 方法来绘制你想要的内容,并根据实际情况进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值