tools.js 6.26 KB
import Taro from '@tarojs/taro'

/**
 * @description 生成随机字符串(递归补齐长度)
 * @param {number} length 目标长度
 * @returns {string} 随机字符串
 */
export function randomString(length) {
  let str = Math.random().toString(36).substr(2)
  if (str.length >= length) {
    return str.substr(0, length)
  }
  str += randomString(length - str.length)
  return str
}

/**
 * @description 生成随机 id(常用于 canvasId)
 * @param {string} prefix 前缀
 * @param {number} length 随机段长度
 * @returns {string} 随机 id
 */
export function getRandomId(prefix = 'canvas', length = 10) {
  return prefix + randomString(length)
}

/**
 * @description 将 http 链接转换为 https(小程序部分场景要求 https)
 * @param {string} rawUrl 原始 url
 * @returns {string} 处理后的 url
 */
export function mapHttpToHttps(rawUrl) {
  if (rawUrl.indexOf(':') < 0 || rawUrl.startsWith('http://tmp')) {
    return rawUrl
  }
  const urlComponent = rawUrl.split(':')
  if (urlComponent.length === 2) {
    if (urlComponent[0] === 'http') {
      urlComponent[0] = 'https'
      return `${urlComponent[0]}:${urlComponent[1]}`
    }
  }
  return rawUrl
}

/**
 * @description 获取 rpx 与 px 的换算系数(以 750 设计稿为基准)
 * @returns {number} 系数(screenWidth / 750)
 */
export const getFactor = () => {
  const sysInfo = Taro.getSystemInfoSync()
  const { screenWidth } = sysInfo
  return screenWidth / 750
}

/**
 * @description rpx 转 px
 * @param {number} rpx rpx 值
 * @param {number} factor 换算系数
 * @returns {number} px 值(整数)
 */
export const toPx = (rpx, factor = getFactor()) => parseInt(String(rpx * factor), 10)

/**
 * @description px 转 rpx
 * @param {number} px px 值
 * @param {number} factor 换算系数
 * @returns {number} rpx 值(整数)
 */
export const toRpx = (px, factor = getFactor()) => parseInt(String(px / factor), 10)

/**
 * @description 下载图片到本地临时路径(避免跨域/协议限制)
 * - 已是本地路径/用户数据路径时直接返回
 * @param {string} url 图片地址
 * @returns {Promise<string>} 本地可用的图片路径
 */
export function downImage(url) {
  return new Promise((resolve, reject) => {
    const wx_user_data_path =
      typeof wx !== 'undefined' && wx && wx.env && wx.env.USER_DATA_PATH
        ? wx.env.USER_DATA_PATH
        : ''
    const is_local_user_path = wx_user_data_path ? new RegExp(wx_user_data_path).test(url) : false

    if (/^http/.test(url) && !is_local_user_path) {
      Taro.downloadFile({
        url: mapHttpToHttps(url),
        success: res => {
          if (res.statusCode === 200) {
            resolve(res.tempFilePath)
          } else {
            reject(res)
          }
        },
        fail(err) {
          reject(err)
        }
      })
    } else {
      resolve(url)
    }
  })
}

/**
 * @description 获取图片信息并计算裁剪参数(居中裁剪)
 * @param {Object} item 图片配置
 * @param {number} index 渲染顺序(默认 zIndex)
 * @returns {Promise<Object>} 标准化后的图片绘制参数
 */
export const getImageInfo = (item, index) =>
  new Promise((resolve, reject) => {
    const { x, y, width, height, url, zIndex } = item
    downImage(url).then(imgPath =>
      Taro.getImageInfo({ src: imgPath })
        .then(imgInfo => {
          let sx
          let sy
          const borderRadius = item.borderRadius || 0
          const imgWidth = toRpx(imgInfo.width)
          const imgHeight = toRpx(imgInfo.height)
          if (imgWidth / imgHeight <= width / height) {
            sx = 0
            sy = (imgHeight - (imgWidth / width) * height) / 2
          } else {
            sy = 0
            sx = (imgWidth - (imgHeight / height) * width) / 2
          }
          const result = {
            type: 'image',
            borderRadius,
            borderWidth: item.borderWidth,
            borderColor: item.borderColor,
            borderRadiusGroup: item.borderRadiusGroup,
            zIndex: typeof zIndex !== 'undefined' ? zIndex : index,
            imgPath: url,
            sx,
            sy,
            sw: imgWidth - sx * 2,
            sh: imgHeight - sy * 2,
            x,
            y,
            w: width,
            h: height
          }
          resolve(result)
        })
        .catch(err => {
          reject(err)
        })
    )
  })

/**
 * @description 解析 linear-gradient 字符串为 canvas 渐变色
 * @param {CanvasRenderingContext2D} ctx canvas 上下文
 * @param {string} color 颜色字符串(支持 linear-gradient(...))
 * @param {number} startX 起点 x
 * @param {number} startY 起点 y
 * @param {number} w 宽度
 * @param {number} h 高度
 * @returns {any} 普通颜色字符串或渐变对象
 */
export function getLinearColor(ctx, color, startX, startY, w, h) {
  if (
    typeof startX !== 'number' ||
    typeof startY !== 'number' ||
    typeof w !== 'number' ||
    typeof h !== 'number'
  ) {
    return color
  }
  let grd = color
  if (color.includes('linear-gradient')) {
    const colorList = color.match(/\((\d+)deg,\s(.+)\s\d+%,\s(.+)\s\d+%/)
    const radian = colorList[1]
    const color1 = colorList[2]
    const color2 = colorList[3]

    const L = Math.sqrt(w * w + h * h)
    const x = Math.ceil(Math.sin(180 - radian) * L)
    const y = Math.ceil(Math.cos(180 - radian) * L)

    if (Number(radian) === 180 || Number(radian) === 0) {
      if (Number(radian) === 180) {
        grd = ctx.createLinearGradient(startX, startY, startX, startY + h)
      }
      if (Number(radian) === 0) {
        grd = ctx.createLinearGradient(startX, startY + h, startX, startY)
      }
    } else if (radian > 0 && radian < 180) {
      grd = ctx.createLinearGradient(startX, startY, x + startX, y + startY)
    } else {
      throw new Error('只支持0 <= 颜色弧度 <= 180')
    }
    grd.addColorStop(0, color1)
    grd.addColorStop(1, color2)
  }
  return grd
}

/**
 * @description 根据 textAlign 计算文本绘制起点 x
 * @param {'left'|'center'|'right'} textAlign 对齐方式
 * @param {number} x 原始 x
 * @param {number} width 容器宽
 * @returns {number} 计算后的 x
 */
export function getTextX(textAlign, x, width) {
  let newX = x
  if (textAlign === 'center') {
    newX = width / 2 + x
  } else if (textAlign === 'right') {
    newX = width + x
  }
  return newX
}