将微信小程序页面转为图片

这篇具有很好参考价值的文章主要介绍了将微信小程序页面转为图片。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最近做项目遇到一个需求,那就是要将某个页面转为图片然后传给后端,我仔细找了一圈,发现官方那个Api也就是wx.canvasToTempFilePath生成的图片很有可能为空,太坑了,于是我放弃用它了,选择了用wxml2canvas。

安装wxml2canvas

npm init
npm install wxml2canvas --save --production

npm init 是npm初始化,这个时候根据编译器终端一路回车最终会生成一个package.json文件
–production 是减少安装与业务无关的包,减少项目的体积。如果没有构建npm,需要我们工具-构建npm,或者勾选中
将微信小程序页面转为图片
如果你没看到这一项也别急,我们高级版本的微信开发者工具默认支持npm,默认就构建了。然后我们要在utils文件夹下新建index.js,这个文件作用就是为了将页面转为图片的,其代码如下:

//此js文件主要用于将页面转图片

import Util from './util'

const imageMode = [
  'scaleToFill',
  'aspectFit',
  'aspectFill',
  'widthFix',
  'top',
  'bottom',
  'center',
  'left',
  'right',
  'top left',
  'top right',
  'bottom left',
  'bottom right',
]

class Wxml2Canvas {
  constructor(options = {}) {
    this.device = (wx.getSystemInfoSync && wx.getSystemInfoSync()) || {}

    if (!options.zoom) {
      this.zoom = this.device.windowWidth / 375
    } else {
      this.zoom = options.zoom || 1
    }

    this.element = options.element
    this.object = options.obj
    this.width = options.width * this.zoom || 0
    this.height = options.height * this.zoom || 0
    this.destZoom = options.destZoom || 3
    this.destWidth = this.width * this.destZoom
    this.destHeight = this.height * this.destZoom
    this.translateX = options.translateX * this.zoom || 0
    this.translateY = options.translateY * this.zoom || 0
    this.gradientBackground = options.gradientBackground || null
    this.background = options.background || '#ffffff'
    this.finishDraw = options.finish || function finish(params) {}
    this.errorHandler = options.error || function error(params) {}
    this.progress = options.progress || function progress(params) {}
    this.textAlign = options.textAlign || 'left'
    this.fullText = options.fullText || false
    this.font = options.font || '14px PingFang SC'

    this._init()
  }

  draw(data = {}, that) {
    let self = this
    this.data = data
    this.fef = that

    this.progress(10)
    this._preloadImage(data.list)
      .then((result) => {
        this.progress(30)
        self._draw()
      })
      .catch((res) => {
        self.errorHandler(res)
      })
  }

  measureWidth(text, font) {
    if (font) {
      this.ctx.font = font
    }
    let res = this.ctx.measureText(text) || {}
    return res.width || 0
  }

  _init() {
    this.progressPercent = 0 // 绘制进度百分比
    this.data = null
    this.ref = null
    this.allPic = []
    this.screenList = []
    this.asyncList = []
    this.imgUrl = ''
    this.progressPercent = 0
    this.distance = 0
    this.progress(0)

    this.ctx = wx.createCanvasContext(this.element, this.obj)
    this.ctx.font = this.font
    this.ctx.setTextBaseline('top')
    this.ctx.setStrokeStyle('white')

    this.debug = this.device.platform === 'devtools' ? true : false

    this._drawBakcground()
  }

  _drawBakcground() {
    if (this.gradientBackground) {
      let line = this.gradientBackground.line || [0, 0, 0, this.height]
      let color = this.gradientBackground.color || ['#fff', '#fff']
      let style = { fill: { line, color } }
      this._drawRectToCanvas(0, 0, this.width, this.height, style)
    } else {
      let style = { fill: this.background }
      this._drawRectToCanvas(0, 0, this.width, this.height, style)
    }
  }

  _draw() {
    let self = this
    let list = this.data.list || []
    let index = 0
    let all = []
    let count = 0

    list.forEach((item) => {
      if (item.type === 'wxml') {
        count += 3
      } else {
        count += 1
      }
    })

    this.distance = 60 / (count || 1) // 进度条的间距
    this.progressPercent = 30
    this.asyncList = list.filter((item) => item.delay == true)
    list = list.filter((item) => item.delay != true)
    drawList(list)

    Promise.all(all)
      .then((results) => {
        index = 0
        drawList(self.asyncList, true)

        Promise.all(all).then((results) => {
          self.progress(90)
          self._saveCanvasToImage()
        })
      })
      .catch((e) => {
        console.log(e)
        self.errorHandler(e)
      })

    function drawList(list = [], noDelay) {
      list.forEach((item, i) => {
        all[index++] = new Promise((resolve, reject) => {
          let attr = item.style
          item.progress = self.distance
          if (noDelay) {
            item.delay = 0
          }
          if (item.type === 'radius-image') {
            self._drawCircle(item, attr, resolve, reject, 'image')
          } else if (item.type === 'text') {
            self._drawText(item, attr, resolve, reject)
          } else if (item.type === 'line') {
            self._drawLine(item, attr, resolve, reject)
          } else if (item.type === 'circle') {
            self._drawCircle(item, attr, resolve, reject)
          } else if (item.type === 'rect') {
            self._drawRect(item, attr, resolve, reject)
          } else if (item.type === 'image') {
            self._drawRect(item, attr, resolve, reject, 'image')
          } else if (item.type === 'wxml') {
            self._drawWxml(item, attr, resolve, reject)
          } else {
            resolve()
          }
        })
      })
    }
  }

  _saveCanvasToImage() {
    let self = this

    // 延时保存有两个原因,一个是等待绘制delay的元素,另一个是安卓上样式会错乱
    setTimeout(
      () => {
        self.progress(95)

        let obj = {
          x: 0,
          y: 0,
          width: self.width,
          height: self.height,
          canvasId: self.element,
          success: function (res) {
            self.progress(100)
            self.imgUrl = res.tempFilePath
            self.finishDraw(self.imgUrl)
          },
          fail: function (res) {
            self.errorHandler({
              errcode: 1000,
              errmsg: 'save canvas error',
              e: res,
            })
          },
        }

        if (self.destZoom !== 3) {
          obj.destWidth = self.destWidth
          obj.destHeight = self.destHeight
        }

        wx.canvasToTempFilePath(obj, self.object)
      },
      self.device.system.indexOf('iOS') === -1 ? 300 : 100,
    )
  }

  _preloadImage(list = []) {
    let self = this
    let all = []
    let count = 0

    list.forEach((item, i) => {
      if (item.url && self._findPicIndex(item.url) === -1) {
        // 避免重复下载同一图片
        self.allPic.push({
          url: item.url,
          local: '',
        })
        all[count++] = new Promise((resolve, reject) => {
          // 非http(s)域名的就不下载了
          if (
            !/^http/.test(item.url) ||
            /^http:\/\/(tmp)|(usr)\//.test(item.url) ||
            /^http:\/\/127\.0\.0\.1/.test(item.url)
          ) {
            if (item.isBase64) {
              let fileManager = wx.getFileSystemManager()

              fileManager.writeFile({
                filePath: item.url,
                data: item.isBase64.replace(/data:image\/(.*);base64,/, ''),
                encoding: 'base64',
                success(res) {
                  imageInfo(item.url)
                },
                fail(res) {
                  reject(res)
                },
              })
            } else {
              imageInfo(item.url)
            }

            function imageInfo(url) {
              wx.getImageInfo({
                src: url,
                success(res) {
                  let index = self._findPicIndex(url)
                  if (index > -1) {
                    self.allPic[index].local = url
                    self.allPic[index].width = res.width
                    self.allPic[index].height = res.height
                  }
                  resolve({ tempFilePath: url })
                },
                fail(res) {
                  reject(res)
                },
              })
            }
          } else {
            wx.downloadFile({
              url: item.url.replace(/^https?/, 'https'),
              success: function (res) {
                wx.getImageInfo({
                  src: res.tempFilePath,
                  success(img) {
                    let index = self._findPicIndex(item.url)
                    if (index > -1) {
                      self.allPic[index].local = res.tempFilePath
                      self.allPic[index].width = img.width
                      self.allPic[index].height = img.height
                    }
                    resolve(res)
                  },
                  fail(res) {
                    reject(res)
                  },
                })
              },
              fail: (res) => {
                reject({ errcode: 1001, errmsg: 'download pic error' })
              },
            })
          }
        })
      }
    })

    return Promise.all(all)
      .then((results) => {
        return new Promise((resolve) => {
          resolve()
        })
      })
      .catch((results) => {
        return new Promise((resolve, reject) => {
          reject(results)
        })
      })
  }

  _findPicIndex(url) {
    let index = this.allPic.findIndex((pic) => pic.url === url)
    return index
  }

  _drawRect(item, style, resolve, reject, isImage, isWxml) {
    let zoom = this.zoom
    let leftOffset = 0
    let topOffset = 0
    let width = style.width
    let height = style.height
    let imgWidth = style.width
    let imgHeight = style.height
    let mode = null

    try {
      item.x = this._resetPositionX(item, style)
      item.y = this._resetPositionY(item, style)

      let url
      if (isImage) {
        let index = this._findPicIndex(item.url)
        if (index > -1) {
          url = this.allPic[index].local
          imgWidth = this.allPic[index].width
          imgHeight = this.allPic[index].height
        } else {
          url = item.url
        }
      }

      style.padding = style.padding || []
      if (isWxml === 'inline-wxml') {
        item.x = item.x + ((style.padding[3] && style.padding[3]) || 0)
        item.y = item.y + ((style.padding[0] && style.padding[0]) || 0)
      }

      leftOffset =
        item.x + style.width + ((style.padding[1] && style.padding[1]) || 0)

      if (!isWxml) {
        width = width * zoom
        height = height * zoom
      }

      if (
        style.dataset &&
        style.dataset.mode &&
        imageMode.indexOf(style.dataset.mode) > -1
      ) {
        mode = {
          type: style.dataset.mode,
          width: imgWidth,
          height: imgHeight,
        }
      }

      this._drawRectToCanvas(item.x, item.y, width, height, style, url, mode)
      this._updateProgress(item.progress)

      if (resolve) {
        resolve()
      } else {
        return {
          leftOffset,
          topOffset,
        }
      }
    } catch (e) {
      reject &&
        reject({
          errcode: isImage ? 1003 : 1002,
          errmsg: isImage ? 'drawImage error' : 'drawRect error',
          e,
        })
    }
  }

  _drawRectToCanvas(x, y, width, height, style, url, mode) {
    let { fill, border, boxShadow } = style
    this.ctx.save()
    this._drawBoxShadow(boxShadow, (res) => {
      // 真机上填充渐变色时,没有阴影,先画个相等大小的纯色矩形来实现阴影
      if (fill && typeof fill !== 'string' && !this.debug) {
        this.ctx.setFillStyle(res.color || '#ffffff')
        this.ctx.fillRect(x, y, width, height)
      }
    })

    if (url) {
      // 开发者工具有bug,先不裁剪
      if (mode) {
        this._resetImageByMode(url, x, y, width, height, mode)
      } else {
        this.ctx.drawImage(url, x, y, width, height)
      }
    } else {
      this._setFill(fill, () => {
        this.ctx.fillRect(x, y, width, height)
      })
    }

    this._drawBorder(border, style, (border) => {
      let fixBorder = border.width
      this.ctx.strokeRect(
        x - fixBorder / 2,
        y - fixBorder / 2,
        width + fixBorder,
        height + fixBorder,
      )
    })

    this.ctx.draw(true)
    this.ctx.restore()
  }

  _resetImageByMode(url, x, y, width, height, mode) {
    let self = this
    let offsetX = 0
    let offsetY = 0
    let imgWidth = mode.width
    let imgHeight = mode.height

    switch (mode.type) {
      case 'scaleToFill':
        imgWidth = width
        imgHeight = height
        self.ctx.drawImage(url, x, y, width, height)
        break
      case 'widthFix':
        height = width / ((imgWidth || 1) / (imgHeight || 1))
        self.ctx.drawImage(url, x, y, width, height)
        break
      case 'aspectFit':
        if (imgWidth > imgHeight) {
          let realHeight = width / ((imgWidth || 1) / (imgHeight || 1))
          offsetY = -(height - realHeight) / 2
          imgWidth = width
          imgHeight = realHeight
        } else {
          let realWidth = height / ((imgHeight || 1) / (imgWidth || 1))
          offsetX = -(width - realWidth) / 2
          imgWidth = realWidth
          imgHeight = height
        }

        _clip()
        break
      case 'aspectFill':
        if (imgWidth > imgHeight) {
          let realWidth = imgWidth / ((imgHeight || 1) / (height || 1))
          offsetX = (realWidth - width) / 2
          imgWidth = realWidth
          imgHeight = height
        } else {
          let realHeight = imgHeight / ((imgWidth || 1) / (width || 1))
          offsetY = (realHeight - height) / 2
          imgWidth = width
          imgHeight = realHeight
        }

        _clip()
        break
      case 'top left':
        _clip()
        break
      case 'top':
        offsetX = (mode.width - width) / 2
        _clip()
        break
      case 'top right':
        offsetX = mode.width - width
        _clip()
        break
      case 'left':
        offsetY = (mode.height - height) / 2
        _clip()
        break
      case 'center':
        offsetX = (mode.width - width) / 2
        offsetY = (mode.height - height) / 2
        _clip()
        break
      case 'right':
        offsetX = mode.width - width
        offsetY = (mode.height - height) / 2
        _clip()
        break
      case 'bottom left':
        offsetY = mode.height - height
        _clip()
        break
      case 'bottom':
        offsetX = (mode.width - width) / 2
        offsetY = mode.height - height
        _clip()
        break
      case 'bottom right':
        offsetX = mode.width - width
        offsetY = mode.height - height
        _clip()
        break
      default:
        imgWidth = width
        imgHeight = height
        break
    }

    function _clip() {
      self.ctx.save()
      self.ctx.beginPath()
      self.ctx.rect(x, y, width, height)
      self.ctx.clip()
      self.ctx.drawImage(url, x - offsetX, y - offsetY, imgWidth, imgHeight)
      self.ctx.closePath()
      self.ctx.restore()
    }
  }

  _drawText(item, style, resolve, reject, type, isWxml) {
    let zoom = this.zoom
    let leftOffset = 0
    let topOffset = 0

    try {
      style.fontSize = this._parseNumber(style.fontSize)
      let fontSize = Math.ceil((style.fontSize || 14) * zoom)
      this.ctx.setTextBaseline('top')
      this.ctx.font = `${
        style.fontWeight ? style.fontWeight : 'normal'
      } ${fontSize}px ${style.fontFamily || 'PingFang SC'}`
      this.ctx.setFillStyle(style.color || '#454545')

      let text = item.text || ''
      let textWidth = Math.floor(
        this.measureWidth(text, style.font || this.ctx.font),
      )
      let lineHeight = this._getLineHeight(style)
      let textHeight =
        Math.ceil(textWidth / (style.width || textWidth)) * lineHeight
      let width = Math.ceil((style.width || textWidth) * (!isWxml ? zoom : 1))
      let whiteSpace = style.whiteSpace || 'wrap'
      let x = 0
      let y = 0

      if (typeof style.padding === 'string') {
        style.padding = Util.transferPadding(style.padding)
      }
      item.x = this._resetPositionX(item, style)
      item.y = this._resetPositionY(item, style, textHeight)
      this._drawBoxShadow(style.boxShadow)

      if (style.background || style.border) {
        this._drawTextBackgroud(item, style, textWidth, textHeight, isWxml)
      }

      // 行内文本
      if (type === 'inline-text') {
        width = item.maxWidth
        if (item.leftOffset + textWidth > width) {
          // 如果上一个行内元素换行了,这个元素要继续在后面补足一行
          let lineNum = Math.max(Math.floor(textWidth / width), 1)
          let length = text.length
          let singleLength = Math.floor(length / lineNum)
          let widthOffset = item.leftOffset ? item.leftOffset - item.originX : 0
          let {
            endIndex: currentIndex,
            single,
            singleWidth,
          } = this._getTextSingleLine(text, width, singleLength, 0, widthOffset)
          x = this._resetTextPositionX(item, style, singleWidth)
          y = this._resetTextPositionY(item, style)
          this.ctx.fillText(single, x, y)
          leftOffset = x + singleWidth
          topOffset = y

          // 去除第一行补的内容,然后重置
          text = text.substring(currentIndex, text.length)
          currentIndex = 0
          lineNum = Math.max(Math.floor(textWidth / width), 1)
          textWidth = Math.floor(
            this.measureWidth(text, style.font || this.ctx.font),
          )
          item.x = item.originX // 还原换行后的x
          for (let i = 0; i < lineNum; i++) {
            let { endIndex, single, singleWidth } = this._getTextSingleLine(
              text,
              width,
              singleLength,
              currentIndex,
            )
            currentIndex = endIndex
            if (single) {
              x = this._resetTextPositionX(item, style, singleWidth, width)
              y = this._resetTextPositionY(item, style, i + 1)
              this.ctx.fillText(single, x, y)
              if (i === lineNum - 1) {
                leftOffset = x + singleWidth
                topOffset = lineHeight * lineNum
              }
            }
          }

          let last = text.substring(currentIndex, length)
          let lastWidth = this.measureWidth(last)

          if (last) {
            x = this._resetTextPositionX(item, style, lastWidth, width)
            y = this._resetTextPositionY(item, style, lineNum + 1)
            this.ctx.fillText(last, x, y)
            leftOffset = x + lastWidth
            topOffset = lineHeight * (lineNum + 1)
          }
        } else {
          x = this._resetTextPositionX(item, style, textWidth, width)
          y = this._resetTextPositionY(item, style)
          this.ctx.fillText(item.text, x, y)
          leftOffset = x + textWidth
          topOffset = lineHeight
        }
      } else {
        // block文本,如果文本长度超过宽度换行
        if (width && textWidth > width && whiteSpace !== 'nowrap') {
          let lineNum = Math.max(Math.floor(textWidth / width), 1)
          let length = text.length
          let singleLength = Math.floor(length / lineNum)
          let currentIndex = 0

          // lineClamp参数限制最多行数
          if (style.lineClamp && lineNum + 1 > style.lineClamp) {
            lineNum = style.lineClamp - 1
          }

          for (let i = 0; i < lineNum; i++) {
            let { endIndex, single, singleWidth } = this._getTextSingleLine(
              text,
              width,
              singleLength,
              currentIndex,
            )
            currentIndex = endIndex
            x = this._resetTextPositionX(item, style, singleWidth, width)
            y = this._resetTextPositionY(item, style, i)
            this.ctx.fillText(single, x, y)
          }

          // 换行后剩余的文字,超过一行则截断增加省略号
          let last = text.substring(currentIndex, length)
          let lastWidth = this.measureWidth(last)
          if (lastWidth > width) {
            let { single, singleWidth } = this._getTextSingleLine(
              last,
              width,
              singleLength,
            )
            lastWidth = singleWidth
            last = single.substring(0, single.length - 1) + '...'
          }

          x = this._resetTextPositionX(item, style, lastWidth, width)
          y = this._resetTextPositionY(item, style, lineNum)
          this.ctx.fillText(last, x, y)
        } else {
          x = this._resetTextPositionX(item, style, textWidth, width)
          y = this._resetTextPositionY(item, style)
          this.ctx.fillText(item.text, x, y)
        }
      }

      this.ctx.draw(true)

      this._updateProgress(item.progress)

      if (resolve) {
        resolve()
      } else {
        return {
          leftOffset,
          topOffset,
        }
      }
    } catch (e) {
      reject && reject({ errcode: 1004, errmsg: 'drawText error', e: e })
    }
  }

  _drawTextBackgroud(item, style, textWidth, textHeight, isWxml) {
    if (!style.width) return
    let zoom = isWxml ? 1 : this.zoom
    let width = style.width || textWidth
    let height = style.height || textHeight
    let rectStyle = {
      fill: style.background,
      border: style.border,
    }
    style.padding = style.padding || [0, 0, 0, 0]
    width += (style.padding[1] || 0) + (style.padding[3] || 0)
    height += (style.padding[0] || 0) + (style.padding[2] || 0)
    width = width * zoom
    height = height * zoom
    this._drawRectToCanvas(item.x, item.y, width, height, rectStyle)
  }

  _drawCircle(item, style, resolve, reject, isImage, isWxml) {
    let zoom = this.zoom
    let r = style.r
    try {
      item.x = this._resetPositionX(item, style)
      item.y = this._resetPositionY(item, style)

      let url
      if (isImage) {
        let index = this._findPicIndex(item.url)
        if (index > -1) {
          url = this.allPic[index].local
        } else {
          url = item.url
        }
      }

      if (!isWxml) {
        r = r * zoom
      }

      this._drawCircleToCanvas(item.x, item.y, r, style, url)

      this._updateProgress(item.progress)
      resolve && resolve()
    } catch (e) {
      reject &&
        reject({
          errcode: isImage ? 1006 : 1005,
          errmsg: isImage ? 'drawCircleImage error' : 'drawCircle error',
          e,
        })
    }
  }

  _drawCircleToCanvas(x, y, r, style, url) {
    let { fill, border, boxShadow } = style

    this.ctx.save()

    this._drawBoxShadow(boxShadow, (res) => {
      // 真机上填充渐变色时,没有阴影,先画个相等大小的纯色矩形来实现阴影
      if ((fill && typeof fill !== 'string') || (url && res.color)) {
        this.ctx.setFillStyle(res.color || '#ffffff')
        this.ctx.beginPath()
        this.ctx.arc(x + r, y + r, r, 0, 2 * Math.PI)
        this.ctx.closePath()
        this.ctx.fill()
      }
    })

    if (url) {
      this.ctx.save()
      this.ctx.beginPath()
      this.ctx.arc(x + r, y + r, r, 0, 2 * Math.PI)
      this.ctx.clip()
      this.ctx.drawImage(url, x, y, r * 2, r * 2)
      this.ctx.closePath()
      this.ctx.restore()
    } else {
      this._setFill(fill, () => {
        this.ctx.beginPath()
        this.ctx.arc(x + r, y + r, r, 0, 2 * Math.PI)
        this.ctx.closePath()
        this.ctx.fill()
      })
    }

    this._drawBorder(border, style, (border) => {
      this.ctx.beginPath()
      this.ctx.arc(x + r, y + r, r + border.width / 2, 0, 2 * Math.PI)
      this.ctx.stroke()
      this.ctx.closePath()
    })

    this.ctx.draw(true)
    this.ctx.restore()
  }

  _drawLine(item, style, resolve, reject, isWxml) {
    let zoom = this.zoom
    try {
      let x1 = item.x * zoom + this.translateX
      let y1 = item.y * zoom + this.translateY
      let x2 = item.x2 * zoom + this.translateX
      let y2 = item.y2 * zoom + this.translateY
      this._drawLineToCanvas(x1, y1, x2, y2, style)

      this._updateProgress(item.progress)
      resolve && resolve()
    } catch (e) {
      reject && reject({ errcode: 1007, errmsg: 'drawLine error', e })
    }
  }

  _drawLineToCanvas(x1, y1, x2, y2, style) {
    let { stroke, dash, boxShadow } = style

    this.ctx.save()
    if (stroke) {
      this._setStroke(stroke)
    }

    this._drawBoxShadow(boxShadow)

    if (dash) {
      let dash = [style.dash[0] || 5, style.dash[1] || 5]
      let offset = style.dash[2] || 0
      this.ctx.setLineDash(dash, offset || 0)
    }

    this.ctx.moveTo(x1, y1)
    this.ctx.setLineWidth((style.width || 1) * this.zoom)
    this.ctx.lineTo(x2, y2)
    this.ctx.stroke()
    this.ctx.draw(true)
    this.ctx.restore()
  }

  // 废弃,合并到_drawRect
  _drawImage(item, style, resolve, reject, isWxml) {
    let zoom = this.zoom
    try {
      item.x = this._resetPositionX(item, style)
      item.y = this._resetPositionY(item, style)
      item.x = item.x + (style.padding[3] || 0)
      item.y = item.y + (style.padding[0] || 0)

      let index = this._findPicIndex(item.url)
      let url = index > -1 ? this.allPic[index].local : item.url
      this._drawImageToCanvas(
        url,
        item.x,
        item.y,
        style.width * zoom,
        style.height * zoom,
        style,
      )

      this._updateProgress(item.progress)
      resolve && resolve()
    } catch (e) {
      reject && reject({ errcode: 1012, errmsg: 'drawRect error', e })
    }
  }

  // 废弃,合并到_drawRect
  _drawImageToCanvas(url, x, y, width, height, style) {
    let { fill, border, boxShadow } = style
    this.ctx.save()

    this._drawBoxShadow(boxShadow)
    this.ctx.drawImage(url, x, y, width, height)

    this._drawBorder(border, style, (border) => {
      let fixBorder = border.width
      this.ctx.strokeRect(
        x - fixBorder / 2,
        y - fixBorder / 2,
        width + fixBorder,
        height + fixBorder,
      )
    })
    this.ctx.draw(true)
    this.ctx.restore()
  }

  _drawWxml(item, style, resolve, reject) {
    let self = this
    let all = []
    try {
      this._getWxml(item, style).then((results) => {
        // 上 -> 下
        let sorted = self._sortListByTop(results[0])
        let count = 0
        let progress = 0
        Object.keys(sorted).forEach((item) => {
          count += sorted[item].length
        })
        progress = (this.distance * 3) / (count || 1)

        all = this._drawWxmlBlock(item, sorted, all, progress, results[1])
        all = this._drawWxmlInline(item, sorted, all, progress, results[1])

        Promise.all(all)
          .then((results) => {
            resolve && resolve()
          })
          .catch((e) => {
            reject && reject(e)
          })
      })
    } catch (e) {
      reject && reject({ errcode: 1008, errmsg: 'drawWxml error' })
    }
  }

  _drawWxmlBlock(item, sorted, all, progress, results) {
    let self = this
    // 用来限定位置范围,取相对位置
    let limitLeft = results ? results.left : 0
    let limitTop = results ? results.top : 0
    Object.keys(sorted).forEach((top, topIndex) => {
      // 左 -> 右
      let list = sorted[top].sort((a, b) => {
        return a.left - b.left
      })

      list = list.filter(
        (sub) => sub.dataset.type && sub.dataset.type.indexOf('inline') === -1,
      )

      list.forEach((sub, index) => {
        all[index] = new Promise((resolve2, reject2) => {
          sub = self._transferWxmlStyle(sub, item, limitLeft, limitTop)
          sub.progress = progress
          let type = sub.dataset.type
          if (sub.dataset.delay) {
            setTimeout(() => {
              drawWxmlItem()
            }, sub.dataset.delay)
          } else {
            drawWxmlItem()
          }
          function drawWxmlItem() {
            if (type === 'text') {
              self._drawWxmlText(sub, resolve2, reject2)
            } else if (type === 'image') {
              self._drawWxmlImage(sub, resolve2, reject2)
            } else if (type === 'radius-image') {
              self._drawWxmlCircleImage(sub, resolve2, reject2)
            } else if (type === 'background-image') {
              self._drawWxmlBackgroundImage(sub, resolve2, reject2)
            }
          }
        })
      })
    })

    return all
  }

  _drawWxmlInline(item, sorted, all, progress, results) {
    let self = this
    let topOffset = 0
    let leftOffset = 0
    let lastTop = 0
    let limitLeft = results ? results.left : 0
    let limitTop = results ? results.top : 0
    let p = new Promise((resolve2, reject2) => {
      let maxWidth = 0
      let minLeft = Infinity
      let maxRight = 0

      // 找出同一top下的最小left和最大right,得到最大的宽度,用于换行
      Object.keys(sorted).forEach((top) => {
        let inlineList = sorted[top].filter(
          (sub) => sub.dataset.type && sub.dataset.type.indexOf('inline') > -1,
        )
        inlineList.forEach((sub) => {
          if (sub.left < minLeft) {
            minLeft = sub.left
          }
          if (sub.right > maxRight) {
            maxRight = sub.right
          }
        })
      })
      maxWidth = Math.ceil(maxRight - minLeft || self.width)

      Object.keys(sorted).forEach((top, topIndex) => {
        // 左 -> 右
        let list = sorted[top].sort((a, b) => {
          return a.left - b.left
        })

        // 换行的行内元素left放到后面,version2.0.6后无法获取高度,改用bottom值来判断是否换行了
        let position = -1
        for (let i = 0, len = list.length; i < len; i++) {
          if (list[i] && list[i + 1]) {
            if (list[i].bottom > list[i + 1].bottom) {
              position = i
              break
            }
          }
        }

        if (position > -1) {
          list.push(list.splice(position, 1)[0])
        }

        let inlineList = list.filter(
          (sub) => sub.dataset.type && sub.dataset.type.indexOf('inline') > -1,
        )
        let originLeft = inlineList[0] ? inlineList[0].left : 0
        // 换行后和top不相等时,认为是换行了,要清除左边距;当左偏移量大于最大宽度时,也要清除左边距; 当左偏移小于左边距时,也要清除
        if (
          Math.abs(topOffset + lastTop - top) > 2 ||
          leftOffset - originLeft - limitLeft >= maxWidth ||
          leftOffset <= originLeft - limitLeft - 2
        ) {
          leftOffset = 0
        }

        lastTop = +top
        topOffset = 0

        inlineList.forEach((sub, index) => {
          sub = self._transferWxmlStyle(sub, item, limitLeft, limitTop)
          sub.progress = progress
          let type = sub.dataset.type
          if (type === 'inline-text') {
            let drawRes = self._drawWxmlInlineText(sub, leftOffset, maxWidth)
            leftOffset = drawRes.leftOffset
            topOffset = drawRes.topOffset
          } else if (type === 'inline-image') {
            let drawRes = self._drawWxmlImage(sub) || {}
            leftOffset = drawRes.leftOffset || 0
            topOffset = drawRes.topOffset || 0
          }
        })
      })
      resolve2()
    })

    all.push(p)
    return all
  }

  _drawWxmlInlineText(sub, leftOffset = 0, maxWidth) {
    let text = sub.dataset.text || ''
    if (sub.dataset.maxlength && text.length > sub.dataset.maxlength) {
      text = text.substring(0, sub.dataset.maxlength) + '...'
    }

    let textData = {
      text,
      originX: sub.left,
      x: leftOffset ? leftOffset : sub.left,
      y: sub.top,
      progress: sub.progress,
      leftOffset: leftOffset,
      maxWidth: maxWidth, // 行内元素的最大宽度,取决于limit的宽度
    }

    if (sub.backgroundColor !== 'rgba(0, 0, 0, 0)') {
      sub.background = sub.backgroundColor
    } else {
      sub.background = 'rgba(0, 0, 0, 0)'
    }

    if (sub.dataset.background) {
      sub.background = sub.dataset.background
    }

    let res = this._drawText(textData, sub, null, null, 'inline-text', 'wxml')

    return res
  }

  _drawWxmlText(sub, resolve, reject) {
    let text = sub.dataset.text || ''
    if (sub.dataset.maxlength && text.length > sub.dataset.maxlength) {
      text = text.substring(0, sub.dataset.maxlength) + '...'
    }

    let textData = {
      text,
      x: sub.left,
      y: sub.top,
      progress: sub.progress,
    }
    if (sub.backgroundColor !== 'rgba(0, 0, 0, 0)') {
      sub.background = sub.backgroundColor
    } else {
      sub.background = 'rgba(0, 0, 0, 0)'
    }

    if (sub.dataset.background) {
      sub.background = sub.dataset.background
    }

    this._drawText(textData, sub, resolve, reject, 'text', 'wxml')
  }

  _drawWxmlImage(sub, resolve, reject) {
    let imageData = {
      url: sub.dataset.url,
      x: sub.left,
      y: sub.top,
      progress: sub.progress,
    }

    let res = this._drawRect(
      imageData,
      sub,
      resolve,
      reject,
      'image',
      'inline-wxml',
    )

    return res
  }

  _drawWxmlCircleImage(sub, resolve, reject) {
    let imageData = {
      url: sub.dataset.url,
      x: sub.left,
      y: sub.top,
      progress: sub.progress,
    }
    sub.r = sub.width / 2

    this._drawCircle(imageData, sub, resolve, reject, true, 'wxml')
  }

  _drawWxmlBackgroundImage(sub, resolve, reject) {
    let url = sub.dataset.url
    let index = this._findPicIndex(url)
    url = index > -1 ? this.allPic[index].local : url
    let size = sub.backgroundSize.replace(/px/g, '').split(' ')

    let imageData = {
      url: url,
      x: sub.left,
      y: sub.top,
      progress: sub.progress,
    }

    this._drawRect(imageData, sub, resolve, reject, 'image', 'wxml')
  }

  _getWxml(item, style) {
    let self = this
    let query
    if (this.obj) {
      query = wx.createSelectorQuery().in(this.obj)
    } else {
      query = wx.createSelectorQuery()
    }

    let p1 = new Promise((resolve, reject) => {
      // 会触发两次,要限制
      let count = 0
      query
        .selectAll(`${item.class}`)
        .fields(
          {
            dataset: true,
            size: true,
            rect: true,
            computedStyle: [
              'width',
              'height',
              'font',
              'fontSize',
              'fontFamily',
              'fontWeight',
              'fontStyle',
              'textAlign',
              'color',
              'lineHeight',
              'border',
              'borderColor',
              'borderStyle',
              'borderWidth',
              'verticalAlign',
              'boxShadow',
              'background',
              'backgroundColor',
              'backgroundImage',
              'backgroundPosition',
              'backgroundSize',
              'paddingLeft',
              'paddingTop',
              'paddingRight',
              'paddingBottom',
            ],
          },
          (res) => {
            if (count++ === 0) {
              let formated = self._formatImage(res)
              let list = formated.list
              res = formated.res

              self
                ._preloadImage(list)
                .then((result) => {
                  resolve(res)
                })
                .catch((res) => {
                  reject &&
                    reject({
                      errcode: 1009,
                      errmsg: 'drawWxml preLoadImage error',
                    })
                })
            }
          },
        )
        .exec()
    })

    let p2 = new Promise((resolve, reject) => {
      if (!item.limit) {
        resolve({ top: 0, width: self.width / self.zoom })
      }

      query
        .select(`${item.limit}`)
        .fields(
          {
            dataset: true,
            size: true,
            rect: true,
          },
          (res) => {
            resolve(res)
          },
        )
        .exec()
    })

    return Promise.all([p1, p2])
  }

  _getLineHeight(style) {
    let zoom = this.zoom
    if (style.dataset && style.dataset.type) {
      zoom = 1
    }
    let lineHeight
    if (!isNaN(style.lineHeight) && style.lineHeight > style.fontSize) {
      lineHeight = style.lineHeight
    } else {
      style.lineHeight = (style.lineHeight || '') + ''
      lineHeight = +style.lineHeight.replace('px', '')
      lineHeight = lineHeight ? lineHeight : (style.fontSize || 14) * 1.2
    }
    return lineHeight * zoom
  }

  _formatImage(res = []) {
    let list = []
    res.forEach((item, index) => {
      let dataset = item.dataset
      let uid = Util.getUid()
      let filename = `${wx.env.USER_DATA_PATH}/${uid}.png`
      if (
        (dataset.type === 'image' || dataset.type === 'radius-image') &&
        dataset.url
      ) {
        let sub = {
          url: dataset.base64 ? filename : dataset.url,
          isBase64: dataset.base64 ? dataset.url : false,
        }

        res[index].dataset = Object.assign(res[index].dataset, sub)
        list.push(sub)
      } else if (
        dataset.type === 'background-image' &&
        item.backgroundImage.indexOf('url') > -1
      ) {
        let url = item.backgroundImage
          .replace(/url\((\"|\')?/, '')
          .replace(/(\"|\')?\)$/, '')
        let sub = {
          url: dataset.base64 ? filename : url,
          isBase64: dataset.base64 ? url : false,
        }
        res[index].dataset = Object.assign(res[index].dataset, sub)
        list.push(sub)
      }
    })

    return { list, res }
  }

  _updateProgress(distance) {
    this.progressPercent += distance
    this.progress(this.progressPercent)
  }

  _sortListByTop(list = []) {
    let sorted = {}

    // 粗略地认为2px相差的元素在同一行
    list.forEach((item, index) => {
      let top = item.top
      if (!sorted[top]) {
        if (sorted[top - 2]) {
          top = top - 2
        } else if (sorted[top - 1]) {
          top = top - 1
        } else if (sorted[top + 1]) {
          top = top + 1
        } else if (sorted[top + 2]) {
          top = top + 2
        } else {
          sorted[top] = []
        }
      }
      sorted[top].push(item)
    })

    return sorted
  }

  _parseNumber(number) {
    return isNaN(number) ? +(number || '').replace('px', '') : number
  }

  _transferWxmlStyle(sub, item, limitLeft, limitTop) {
    let leftFix = +sub.dataset.left || 0
    let topFix = +sub.dataset.top || 0

    sub.width = this._parseNumber(sub.width)
    sub.height = this._parseNumber(sub.height)
    sub.left =
      this._parseNumber(sub.left) -
      limitLeft +
      (leftFix + (item.x || 0)) * this.zoom
    sub.top =
      this._parseNumber(sub.top) -
      limitTop +
      (topFix + (item.y || 0)) * this.zoom

    let padding = sub.dataset.padding || '0 0 0 0'
    if (typeof padding === 'string') {
      padding = Util.transferPadding(padding)
    }
    let paddingTop =
      Number(sub.paddingTop.replace('px', '')) + Number(padding[0])
    let paddingRight =
      Number(sub.paddingRight.replace('px', '')) + Number(padding[1])
    let paddingBottom =
      Number(sub.paddingBottom.replace('px', '')) + Number(padding[2])
    let paddingLeft =
      Number(sub.paddingLeft.replace('px', '')) + Number(padding[3])
    sub.padding = [paddingTop, paddingRight, paddingBottom, paddingLeft]

    return sub
  }

  /**
   * 支持负值绘制,从右边计算
   * @param {*} item
   * @param {*} style
   */
  _resetPositionX(item, style) {
    let zoom = this.zoom
    let x = 0

    if (style.dataset && style.dataset.type) {
      zoom = 1
    }

    // 通过wxml获取的不需要重置坐标
    if (item.x < 0 && item.type) {
      x = this.width + item.x * zoom - style.width * zoom
    } else {
      x = item.x * zoom
    }

    if (parseInt(style.borderWidth)) {
      x += parseInt(style.borderWidth)
    }

    return x + this.translateX
  }

  /**
   * 支持负值绘制,从底部计算
   * @param {*} item
   * @param {*} style
   */
  _resetPositionY(item, style, textHeight) {
    let zoom = this.zoom
    let y = 0

    if (style.dataset && style.dataset.type) {
      zoom = 1
    }

    if (item.y < 0) {
      y =
        this.height +
        item.y * zoom -
        (textHeight ? textHeight : style.height * zoom)
    } else {
      y = item.y * zoom
    }

    if (parseInt(style.borderWidth)) {
      y += parseInt(style.borderWidth)
    }

    return y + this.translateY
  }

  /**
   * 文字的padding、text-align
   * @param {*} item
   * @param {*} style
   * @param {*} textWidth
   */
  _resetTextPositionX(item, style, textWidth, width) {
    let textAlign = style.textAlign || 'left'
    let x = item.x
    if (textAlign === 'center') {
      x = (width - textWidth) / 2 + item.x
    } else if (textAlign === 'right') {
      x = width - textWidth + item.x
    }

    let left = style.padding ? style.padding[3] || 0 : 0

    return x + left + this.translateX
  }

  /**
   * 文字的padding、text-align
   * @param {*} item
   * @param {*} style
   * @param {*} textWidth
   */
  _resetTextPositionY(item, style, lineNum = 0) {
    let zoom = this.zoom
    if (style.dataset && style.dataset.type) {
      zoom = 1
    }

    let lineHeight = this._getLineHeight(style)
    let fontSize = Math.ceil((style.fontSize || 14) * zoom)

    let blockLineHeightFix =
      ((style.dataset && style.dataset.type) || '').indexOf('inline') > -1
        ? 0
        : (lineHeight - fontSize) / 2

    let top = style.padding ? style.padding[0] || 0 : 0

    // y + lineheight偏移 + 行数 + paddingTop + 整体画布位移
    return (
      item.y + blockLineHeightFix + lineNum * lineHeight + top + this.translateY
    )
  }

  /**
   * 当文本超过宽度时,计算每一行应该绘制的文本
   * @param {*} text
   * @param {*} width
   * @param {*} singleLength
   * @param {*} currentIndex
   * @param {*} widthOffset
   */
  _getTextSingleLine(
    text,
    width,
    singleLength,
    currentIndex = 0,
    widthOffset = 0,
  ) {
    let offset = 0
    let endIndex = currentIndex + singleLength + offset
    let single = text.substring(currentIndex, endIndex)
    let singleWidth = this.measureWidth(single)

    while (Math.round(widthOffset + singleWidth) > width) {
      offset--
      endIndex = currentIndex + singleLength + offset
      single = text.substring(currentIndex, endIndex)
      singleWidth = this.measureWidth(single)
    }

    return {
      endIndex,
      single,
      singleWidth,
    }
  }

  _drawBorder(border, style, callback) {
    let zoom = this.zoom
    if (style.dataset && style.dataset.type) {
      zoom = 1
    }
    border = Util.transferBorder(border)

    if (border && border.width) {
      // 空白阴影,清空掉边框的阴影
      this._drawBoxShadow()
      if (border) {
        this.ctx.setLineWidth(border.width * zoom)

        if (border.style === 'dashed') {
          let dash = style.dash || [5, 5, 0]
          let offset = dash[2] || 0
          let array = [dash[0] || 5, dash[1] || 5]
          this.ctx.setLineDash(array, offset)
        }
        this.ctx.setStrokeStyle(border.color)
      }
      callback && callback(border)
    }
  }

  _drawBoxShadow(boxShadow, callback) {
    boxShadow = Util.transferBoxShadow(boxShadow)
    if (boxShadow) {
      this.ctx.setShadow(
        boxShadow.offsetX,
        boxShadow.offsetY,
        boxShadow.blur,
        boxShadow.color,
      )
    } else {
      this.ctx.setShadow(0, 0, 0, '#ffffff')
    }

    callback && callback(boxShadow || {})
  }

  _setFill(fill, callback) {
    if (fill) {
      if (typeof fill === 'string') {
        this.ctx.setFillStyle(fill)
      } else {
        let line = fill.line
        let color = fill.color
        let grd = this.ctx.createLinearGradient(
          line[0],
          line[1],
          line[2],
          line[3],
        )
        grd.addColorStop(0, color[0])
        grd.addColorStop(1, color[1])
        this.ctx.setFillStyle(grd)
      }
      callback && callback()
    }
  }

  _setStroke(stroke, callback) {
    if (stroke) {
      if (typeof stroke === 'string') {
        this.ctx.setStrokeStyle(stroke)
      } else {
        let line = stroke.line
        let color = stroke.color
        let grd = this.ctx.createLinearGradient(
          line[0],
          line[1],
          line[2],
          line[3],
        )
        grd.addColorStop(0, color[0])
        grd.addColorStop(1, color[1])
        this.ctx.setStrokeStyle(grd)
      }

      callback && callback()
    }
  }
}

export default Wxml2Canvas

wxml2canvas使用

我们来看个demo,如下图,我的test文件夹下有个img文件夹,里面的test图片就是我的测试图片
将微信小程序页面转为图片
我们的页面有文本有图片,需要将这些所有的内容合起来转为一张大图传给后端,这里需要注意的几个点是:wxml里canvas-id=“canvas1"一定要与new Wxml2Canvas方法里的element相对应,然后canvas的宽高设为100vw和100vh这样生成的图片不会变小了。根元素设置限定范围limit,比如我这里根元素class为my_canvas,那就应该是limit: ‘.my_canvas’,然后每个要单独绘制的元素class都有一个类名my_draw_canvas我们也要与之匹配。然后文本的话text要设置 data-type=“text” data-text=“我是pages/test/test.wxml页面”,其中data-text是文本内容。如果是图片的话,image标签要设置data-type=“image” data-url=”./img/test.jpg",其中data-url是图片地址。这里我用到了wx.getFileSystemManager().readFile将图片临时地址转为base64, 下面是我这个demo的源码:

//test.wxml
<view>
  <view  id="my_canvas" class="my_canvas box" >
    <text class="my_draw_canvas tips" data-type="text" data-text="我是pages/test/test.wxml页面">我是pages/test/test.wxml页面</text>
    <text class="my_draw_canvas explain" data-type="text" data-text="测试wxml2canvas转图片功能">测试wxml2canvas转图片功能</text>
    <image class="my_draw_canvas img" data-type="image" data-url="./img/test.jpg" src="./img/test.jpg"></image>
  </view>
  <button type="primary"  class="btn" bindtap="drawImage1">点我进行转换</button>

  <view class="result">
      <text>下面是用canvas生成的图片:</text>
      <image class="resultImg" style="width:100vw;height:100vh"  src="{{FinallySign}}" mode=""/>
  </view>

</view>

<canvas canvas-id="canvas1" style="width:100vw;height:100vh"></canvas>

//test.wxss
.box{
    text-align: center;
}
.box .img{
  height: 100vh;
}

.box .tips{
    display: inline-block;
    width: 100%;
}

.btn{
    width:60%;
    margin:30rpx auto;
}

.result{
  
    text-align: center;
}
.result text{
    margin-bottom:50rpx;
}
.result .resultImg{
    width:100%;
}

//test.js
// pages/test.js

import Wxml2Canvas from '../../src/index';

Page({

    /**
     * 页面的初始数据
     */
    data: {
        FinallySign:'',
        imageWidth: '', //画在画布上的图片的宽度
        imageHeight: '', //画在画布上的图片的高度
    },
    drawImage1 () {
        let that = this;
        let drawMyImage  = new Wxml2Canvas({
            // width: 340,
            // height: 210,
            width: that.data.imageWidth * 2 ,
            height: that.data.imageHeight * 2 + 'px',
            element: 'canvas1',
            background: '#f0f0f0',
            progress (percent) {
                
            },
            finish(url) {
                console.log("生成的图片地址",url)
                wx.getFileSystemManager().readFile({
                    filePath: url,
                    encoding: 'base64',
                    success: (res) => {
                      let MyImageBase64 = 'data:image/jpg;base64,' + res.data
                      console.log('MyImageBase64', MyImageBase64)
                      that.setData({
                        FinallySign: MyImageBase64,
                      })
                    },
                  })

             
            },
            error (res) {
                console.log("生成的图片失败",res)
            }
        },this);

        let data = {
            list: [{
                type: 'wxml',
                class: '.my_canvas .my_draw_canvas', //.my_draw_canvas每个要绘制元素的类名
                limit: '.my_canvas', //my_canvas根元素类名
                x: 0,
                y: 0
            }]
        }

        drawMyImage.draw(data,that);
    },

    /**
     * 生命周期函数--监听页面显示
     */
    onShow() {
        const that = this
        const query = wx.createSelectorQuery().in(this)

    query
      .select('#my_canvas')
      .fields(
        {
          // 选择需要生成canvas的范围
          size: true,
          node: true,
          scrollOffset: true,
        },
        (data) => {
          let width = data.width
          let height = data.height
          that.setData({
            imageWidth: width,
            imageHeight: height,
          })
        },
      )
      .exec()
    }

 
})

下面附上github地址:

https://gitcode.net/mirrors/liudongyun1215/wxml2canvas?utm_source=csdn_github_accelerator

将微信小程序页面转为图片
将微信小程序页面转为图片
将微信小程序页面转为图片
将微信小程序页面转为图片
将微信小程序页面转为图片
将微信小程序页面转为图片
将微信小程序页面转为图片
将微信小程序页面转为图片
将微信小程序页面转为图片文章来源地址https://www.toymoban.com/news/detail-501192.html

到了这里,关于将微信小程序页面转为图片的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用 HBuilder X 将微信 小程序 项目 打包

    提示:这里简述项目相关背景: 使用 HBuilder X 将微信 小程序 项目 打包 这里描述项目中遇到的问题:` 使用 HBuilder X 将微信 小程序 项目 打包 这里填写该问题的具体解决方案: 第一步:使用 HBuilder X 将项目 打开、 第二步: 将 微信小程序项目 进行 打包 第三步:一般在打包

    2024年02月16日
    浏览(42)
  • 【uniapp】将微信小程序的代码兼容支付宝小程序(持续更新)

    目前本身就有一套完善的微信小程序(兼容h5)的代码,现在的需求是将它编译成支付宝小程序,做好兼容的处理,以便后续接入支付宝服务商,在这里简单记录一下目前发现的把微信小程序编译成支付宝小程序的问题和解决方案。 建议配合其他人的记录一起看,这里只是我

    2024年02月09日
    浏览(157)
  • 微信小程序点击图片放大预览,新页面中全屏预览图片

    第一步:在wxml中定义image组件,并设置绑定事件。 第二步:在js中设置需要预览图片的URL数组, 切记一定要是数组 ,即使一张图也要是数组,不能直接字符串赋值。 2.1 data数据设置 2.2 绑定事件函数编制 3.wx.previewImage组件官方调用指南 4、效果预览

    2024年02月11日
    浏览(73)
  • uni-app 微信小程序 保存当前页面为图片

    由于在微信小程序环境下面没法获取dom,很多方法都很难去实现保存html结构的页面,比较有效的#painter 可以不需要操作dom,但是那玩意儿和重新用js写个页面一样,简单的页面还好,复杂的,元素比较多的就很麻烦,所以考虑用webview+html2canvas来完成 先说一下思路,既然在微

    2024年02月11日
    浏览(55)
  • 微信小程序将后端返回的图片文件流解析显示到页面

    由于请求接口后端返回的图片格式不是一个完整的url,也不是其他直接能显示的图片格式,是一张图片 后端根据模板与二维码生成图片,返回二进制数据 返回为文件流的格式,用wx.request请求的时候,就自动解码成为了下面这样的数据数据格式,这样的数据没有办法直接赋值给ur

    2024年02月02日
    浏览(54)
  • 微信小程序 editor图片上传到node服务器并展示在当前页面

    前端  html js node后端 效果  小程序端     后台  

    2024年02月11日
    浏览(50)
  • uni-app:使用 Painter 在微信小程序将当前页面保存为图片

    手机截屏 Painter 实现 方式一:Painter Painter 是一个微信小程序组件,具体介绍和 API 请参考:GitHub文档。 在 GitHub 下载下来之后只需要将 components 下的 painter 文件夹放到项目根目录下的 wxcomponents 文件夹即可。然后就是如何在 uni-app 中使用微信小程序形式的组件,其实很简单,

    2024年02月12日
    浏览(71)
  • 无插件,简单配置prettier,将微信小程序的wxml和wxss当作html和css进行格式化

    使用prettier的项目、安装了prettier扩展的vscode和微信开发者工具。 prettier是根据默认的解析器对相应的文件进行处理,我们可以自定义什么格式的文件用什么解析器。具体看 documentSelectors。 只列出有关的配置 编辑器的配置 设置wxml和wxss文件的默认格式化工具为prettier 配置pre

    2024年02月09日
    浏览(66)
  • 微信小程序之项目基本结构、页面的基础及宿主环境

    微信小程序的项目基本结构、页面的基础及宿主环境 新建一个微信小程序项目,其项目基本结构如下: pages用来存放所有小程序的页面 utils用来存放工具性质的模块(例如:格式化时间的自定义模板) app.js小程序项目的全局配置文件 app.json小程序项目的全局配置文件 app.wx

    2024年02月03日
    浏览(50)
  • 【图片消消乐】单机游戏-微信小程序项目开发入门

    这是一个微信小程序项目,是类似开心消消乐的休闲小游戏,老少皆宜,游戏互动里面的图片是用的任何图片素材,根据自己的需求更换图片即可。想要做游戏不知道怎么做,建议从这个小程序入手,花时间研究学习,很快就拥有属于自己的小程序。 准备 会使用微信开发工

    2024年02月10日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包