起因:非type=”2d”的canvas不支持同层渲染,因为无法设定其z-index等级。所以随手写了一个符合type=”2d”的生成条形码的Demo

index.wxml

<div class="code">
  <canvas type="2d" id="myCanvas"></canvas>
  <image src="{{ radarImg }}" />
</div>

index.js

Page({
  data: {
    radarImg: ''
  },
  onLoad() {
    this.getCodeImg()
  },
  async getCodeImg() {
    const wxbarcode = require('../plus/barcode/barcode.js')
    let imgUrl = await wxbarcode.barcode('#myCanvas', '12345678901', 400, 100)
    
    this.setData({
      radarImg: imgUrl
    })
  }
})

plus/barcode/barcode.js

const CHAR_TILDE = 126
const CODE_FNC1 = 102

const SET_STARTA = 103
const SET_STARTB = 104
const SET_STARTC = 105
const SET_SHIFT = 98
const SET_CODEA = 101
const SET_CODEB = 100
const SET_STOP = 106

const REPLACE_CODES = {
  CHAR_TILDE: CODE_FNC1 //~ corresponds to FNC1 in GS1-128 standard
}

const CODESET = {
  ANY: 1,
  AB: 2,
  A: 3,
  B: 4,
  C: 5
}

const barcode = (id, code, width, height) => {

  const codes = stringToCode128(code)

  console.log('转换后的code:', codes)

  return new Promise((resolve, reject) => {
    // 获取 canvas Dom结构
    const query = wx.createSelectorQuery()
    query.select(id)
      .fields({ node: true, size: true })
      .exec(async (res) => {
        const canvas = res[0].node
        const ctx = canvas.getContext('2d')

        // 绘制
        let reImg = await drawGraphics(ctx, canvas, codes, convertLength(width), convertLength(height))
        resolve(reImg)
      })
  })
}

// 绘制条形码
const drawGraphics = async (ctx, canvas, codes, width, height) => {
  const g = new Graphics(ctx, width, height)

  const barWeight = g.area.width / ((codes.length - 3) * 11 + 35)

  let x = g.area.left
  let y = g.area.top

  for (let i = 0; i < codes.length; i++) {
    let c = codes[i]
    //two bars at a time: 1 black and 1 white
    for (let bar = 0; bar < 8; bar += 2) {
      let barW = PATTERNS[c][bar] * barWeight;
      // let barH = height - y - this.border;
      let barH = height - y;
      let spcW = PATTERNS[c][bar + 1] * barWeight;

      //no need to draw if 0 width
      if (barW > 0) {
        g.fillFgRect(x, y, barW, barH)
      }
      x += barW + spcW;
    }
  }

  let resImg = await wx.canvasToTempFilePath({
    x: 0,
    y: 0,
    width: 320,
    height: 150,
    canvas: canvas
  })

  return new Promise((resolve, reject) => {
    if (resImg.tempFilePath) {
      resolve(resImg.tempFilePath)
    } else {
      reject('error')
    }
  })
}

// 条形码
const Graphics = function (ctx, width, height) {
  this.width = width;
  this.height = height;
  this.quiet = Math.round(this.width / 40);

  this.border_size = 0;
  this.padding_width = 0;

  this.area = {
    width: width - this.padding_width * 2 - this.quiet * 2,
    height: height - this.border_size * 2,
    top: this.border_size - 4,
    left: this.padding_width + this.quiet
  };

  this.ctx = ctx;
  this.fg = "#000000";
  this.bg = "#ffffff";

  // fill background
  this.fillBgRect(0, 0, width, height);

  // fill center to create border
  this.fillBgRect(0, this.border_size, width, height - this.border_size * 2)
}

//use native color
Graphics.prototype._fillRect = function (x, y, width, height, color) {
  this.ctx.fillStyle = color
  this.ctx.fillRect(x, y, width, height)
}

Graphics.prototype.fillFgRect = function (x, y, width, height) {
  this._fillRect(x, y, width, height, this.fg);
}

Graphics.prototype.fillBgRect = function (x, y, width, height) {
  this._fillRect(x, y, width, height, this.bg);
}

// 计算分辨率
const convertLength = (length) => {
  return Math.round(wx.getSystemInfoSync().windowWidth * length / 750)
}

// 转换code
const stringToCode128 = (code) => {
  const barc = {
    currcs: CODESET.C
  }

  let bytes = getBytes(code)
  let index = bytes[0] == CHAR_TILDE ? 1 : 0

  let csa1 = bytes.length > 0 ? codeSetAllowedFor(bytes[index++]) : CODESET.AB
  let csa2 = bytes.length > 0 ? codeSetAllowedFor(bytes[index++]) : CODESET.AB

  barc.currcs = getBestStartSet(csa1, csa2)
  barc.currcs = perhapsCodeC(bytes, barc.currcs)

  //if no codeset changes this will end up with bytes.length+3
  //start, checksum and stop
  let codes = new Array()

  switch (barc.currcs) {
    case CODESET.A:
      codes.push(SET_STARTA)
      break
    case CODESET.B:
      codes.push(SET_STARTB)
      break
    default:
      codes.push(SET_STARTC)
      break
  }

  for (let i = 0; i < bytes.length; i++) {
    let b1 = bytes[i]; //get the first of a pair
    //should we translate/replace
    if (b1 in REPLACE_CODES) {
      codes.push(REPLACE_CODES[b1]);
      i++ //jump to next
      b1 = bytes[i];
    }

    //get the next in the pair if possible
    let b2 = bytes.length > (i + 1) ? bytes[i + 1] : -1;

    codes = codes.concat(codesForChar(b1, b2, barc.currcs, barc));
    //code C takes 2 chars each time
    if (barc.currcs == CODESET.C) i++;
  }

  //calculate checksum according to Code 128 standards
  let checksum = codes[0]
  for (let weight = 1; weight < codes.length; weight++) {
    checksum += (weight * codes[weight])
  }
  codes.push(checksum % 103)

  codes.push(SET_STOP)

  //encoding should now be complete
  return codes

  function getBestStartSet(csa1, csa2) {
    //tries to figure out the best codeset
    //to start with to get the most compact code
    var vote = 0;
    vote += csa1 == CODESET.A ? 1 : 0
    vote += csa1 == CODESET.B ? -1 : 0
    vote += csa2 == CODESET.A ? 1 : 0
    vote += csa2 == CODESET.B ? -1 : 0
    //tie goes to B due to my own predudices
    return vote > 0 ? CODESET.A : CODESET.B
  }

  function perhapsCodeC(bytes, codeset) {
    for (let i = 0; i < bytes.length; i++) {
      let b = bytes[i]
      if ((b < 48 || b > 57) && b != CHAR_TILDE) {
        return codeset
      }
    }
    return CODESET.C
  }

  //chr1 is current byte
  //chr2 is the next byte to process. looks ahead.
  function codesForChar(chr1, chr2, currcs) {
    var result = [];
    var shifter = -1;

    if (charCompatible(chr1, currcs)) {
      if (currcs == CODESET.C) {
        if (chr2 == -1) {
          shifter = SET_CODEB;
          currcs = CODESET.B;
        }
        else if ((chr2 != -1) && !charCompatible(chr2, currcs)) {
          //need to check ahead as well
          if (charCompatible(chr2, CODESET.A)) {
            shifter = SET_CODEA;
            currcs = CODESET.A;
          }
          else {
            shifter = SET_CODEB;
            currcs = CODESET.B;
          }
        }
      }
    }
    else {
      //if there is a next char AND that next char is also not compatible
      if ((chr2 != -1) && !charCompatible(chr2, currcs)) {
        //need to switch code sets
        switch (currcs) {
          case CODESET.A:
            shifter = SET_CODEB;
            currcs = CODESET.B;
            break;
          case CODESET.B:
            shifter = SET_CODEA;
            currcs = CODESET.A;
            break;
        }
      }
      else {
        //no need to shift code sets, a temporary SHIFT will suffice
        shifter = SET_SHIFT;
      }
    }

    //ok some type of shift is nessecary
    if (shifter != -1) {
      result.push(shifter);
      result.push(codeValue(chr1));
    }
    else {
      if (currcs == CODESET.C) {
        //include next as well
        result.push(codeValue(chr1, chr2));
      }
      else {
        result.push(codeValue(chr1));
      }
    }
    barc.currcs = currcs;

    return result;
  }

}

// 转换Bytes
const getBytes = (code) => {
  let bytes = []
  for (let index = 0; index < code.length; index++) {
    bytes.push(code.charCodeAt(index))
  }
  return bytes
}

const codeSetAllowedFor = (chr) => {
  if (chr >= 48 && chr <= 57) {
    //0-9
    return CODESET.ANY
  } else if (chr >= 32 && chr <= 95) {
    //0-9 A-Z
    return CODESET.AB
  } else {
    return chr < 32 ? CODESET.A : CODESET.B
  }
}

// reduce the ascii code to fit into the Code128 char table
const codeValue = (chr1, chr2) => {
  if (typeof chr2 == 'undefined') {
    return chr1 >= 32 ? chr1 - 32 : chr1 + 64;
  } else {
    return parseInt(String.fromCharCode(chr1) + String.fromCharCode(chr2));
  }
}

const charCompatible = (chr, codeset) => {
  var csa = codeSetAllowedFor(chr)
  if (csa == CODESET.ANY) return true
  //if we need to change from current
  if (csa == CODESET.AB) return true
  if (csa == CODESET.A && codeset == CODESET.A) return true
  if (csa == CODESET.B && codeset == CODESET.B) return true
  return false
}

const PATTERNS = [
  [2, 1, 2, 2, 2, 2, 0, 0],  // 0
  [2, 2, 2, 1, 2, 2, 0, 0],  // 1
  [2, 2, 2, 2, 2, 1, 0, 0],  // 2
  [1, 2, 1, 2, 2, 3, 0, 0],  // 3
  [1, 2, 1, 3, 2, 2, 0, 0],  // 4
  [1, 3, 1, 2, 2, 2, 0, 0],  // 5
  [1, 2, 2, 2, 1, 3, 0, 0],  // 6
  [1, 2, 2, 3, 1, 2, 0, 0],  // 7
  [1, 3, 2, 2, 1, 2, 0, 0],  // 8
  [2, 2, 1, 2, 1, 3, 0, 0],  // 9
  [2, 2, 1, 3, 1, 2, 0, 0],  // 10
  [2, 3, 1, 2, 1, 2, 0, 0],  // 11
  [1, 1, 2, 2, 3, 2, 0, 0],  // 12
  [1, 2, 2, 1, 3, 2, 0, 0],  // 13
  [1, 2, 2, 2, 3, 1, 0, 0],  // 14
  [1, 1, 3, 2, 2, 2, 0, 0],  // 15
  [1, 2, 3, 1, 2, 2, 0, 0],  // 16
  [1, 2, 3, 2, 2, 1, 0, 0],  // 17
  [2, 2, 3, 2, 1, 1, 0, 0],  // 18
  [2, 2, 1, 1, 3, 2, 0, 0],  // 19
  [2, 2, 1, 2, 3, 1, 0, 0],  // 20
  [2, 1, 3, 2, 1, 2, 0, 0],  // 21
  [2, 2, 3, 1, 1, 2, 0, 0],  // 22
  [3, 1, 2, 1, 3, 1, 0, 0],  // 23
  [3, 1, 1, 2, 2, 2, 0, 0],  // 24
  [3, 2, 1, 1, 2, 2, 0, 0],  // 25
  [3, 2, 1, 2, 2, 1, 0, 0],  // 26
  [3, 1, 2, 2, 1, 2, 0, 0],  // 27
  [3, 2, 2, 1, 1, 2, 0, 0],  // 28
  [3, 2, 2, 2, 1, 1, 0, 0],  // 29
  [2, 1, 2, 1, 2, 3, 0, 0],  // 30
  [2, 1, 2, 3, 2, 1, 0, 0],  // 31
  [2, 3, 2, 1, 2, 1, 0, 0],  // 32
  [1, 1, 1, 3, 2, 3, 0, 0],  // 33
  [1, 3, 1, 1, 2, 3, 0, 0],  // 34
  [1, 3, 1, 3, 2, 1, 0, 0],  // 35
  [1, 1, 2, 3, 1, 3, 0, 0],  // 36
  [1, 3, 2, 1, 1, 3, 0, 0],  // 37
  [1, 3, 2, 3, 1, 1, 0, 0],  // 38
  [2, 1, 1, 3, 1, 3, 0, 0],  // 39
  [2, 3, 1, 1, 1, 3, 0, 0],  // 40
  [2, 3, 1, 3, 1, 1, 0, 0],  // 41
  [1, 1, 2, 1, 3, 3, 0, 0],  // 42
  [1, 1, 2, 3, 3, 1, 0, 0],  // 43
  [1, 3, 2, 1, 3, 1, 0, 0],  // 44
  [1, 1, 3, 1, 2, 3, 0, 0],  // 45
  [1, 1, 3, 3, 2, 1, 0, 0],  // 46
  [1, 3, 3, 1, 2, 1, 0, 0],  // 47
  [3, 1, 3, 1, 2, 1, 0, 0],  // 48
  [2, 1, 1, 3, 3, 1, 0, 0],  // 49
  [2, 3, 1, 1, 3, 1, 0, 0],  // 50
  [2, 1, 3, 1, 1, 3, 0, 0],  // 51
  [2, 1, 3, 3, 1, 1, 0, 0],  // 52
  [2, 1, 3, 1, 3, 1, 0, 0],  // 53
  [3, 1, 1, 1, 2, 3, 0, 0],  // 54
  [3, 1, 1, 3, 2, 1, 0, 0],  // 55
  [3, 3, 1, 1, 2, 1, 0, 0],  // 56
  [3, 1, 2, 1, 1, 3, 0, 0],  // 57
  [3, 1, 2, 3, 1, 1, 0, 0],  // 58
  [3, 3, 2, 1, 1, 1, 0, 0],  // 59
  [3, 1, 4, 1, 1, 1, 0, 0],  // 60
  [2, 2, 1, 4, 1, 1, 0, 0],  // 61
  [4, 3, 1, 1, 1, 1, 0, 0],  // 62
  [1, 1, 1, 2, 2, 4, 0, 0],  // 63
  [1, 1, 1, 4, 2, 2, 0, 0],  // 64
  [1, 2, 1, 1, 2, 4, 0, 0],  // 65
  [1, 2, 1, 4, 2, 1, 0, 0],  // 66
  [1, 4, 1, 1, 2, 2, 0, 0],  // 67
  [1, 4, 1, 2, 2, 1, 0, 0],  // 68
  [1, 1, 2, 2, 1, 4, 0, 0],  // 69
  [1, 1, 2, 4, 1, 2, 0, 0],  // 70
  [1, 2, 2, 1, 1, 4, 0, 0],  // 71
  [1, 2, 2, 4, 1, 1, 0, 0],  // 72
  [1, 4, 2, 1, 1, 2, 0, 0],  // 73
  [1, 4, 2, 2, 1, 1, 0, 0],  // 74
  [2, 4, 1, 2, 1, 1, 0, 0],  // 75
  [2, 2, 1, 1, 1, 4, 0, 0],  // 76
  [4, 1, 3, 1, 1, 1, 0, 0],  // 77
  [2, 4, 1, 1, 1, 2, 0, 0],  // 78
  [1, 3, 4, 1, 1, 1, 0, 0],  // 79
  [1, 1, 1, 2, 4, 2, 0, 0],  // 80
  [1, 2, 1, 1, 4, 2, 0, 0],  // 81
  [1, 2, 1, 2, 4, 1, 0, 0],  // 82
  [1, 1, 4, 2, 1, 2, 0, 0],  // 83
  [1, 2, 4, 1, 1, 2, 0, 0],  // 84
  [1, 2, 4, 2, 1, 1, 0, 0],  // 85
  [4, 1, 1, 2, 1, 2, 0, 0],  // 86
  [4, 2, 1, 1, 1, 2, 0, 0],  // 87
  [4, 2, 1, 2, 1, 1, 0, 0],  // 88
  [2, 1, 2, 1, 4, 1, 0, 0],  // 89
  [2, 1, 4, 1, 2, 1, 0, 0],  // 90
  [4, 1, 2, 1, 2, 1, 0, 0],  // 91
  [1, 1, 1, 1, 4, 3, 0, 0],  // 92
  [1, 1, 1, 3, 4, 1, 0, 0],  // 93
  [1, 3, 1, 1, 4, 1, 0, 0],  // 94
  [1, 1, 4, 1, 1, 3, 0, 0],  // 95
  [1, 1, 4, 3, 1, 1, 0, 0],  // 96
  [4, 1, 1, 1, 1, 3, 0, 0],  // 97
  [4, 1, 1, 3, 1, 1, 0, 0],  // 98
  [1, 1, 3, 1, 4, 1, 0, 0],  // 99
  [1, 1, 4, 1, 3, 1, 0, 0],  // 100
  [3, 1, 1, 1, 4, 1, 0, 0],  // 101
  [4, 1, 1, 1, 3, 1, 0, 0],  // 102
  [2, 1, 1, 4, 1, 2, 0, 0],  // 103
  [2, 1, 1, 2, 1, 4, 0, 0],  // 104
  [2, 1, 1, 2, 3, 2, 0, 0],  // 105
  [2, 3, 3, 1, 1, 1, 2, 0]   // 106
]

module.exports = {
  barcode: barcode
}

本Demo即实现了使用canvas绘制条形码的方法,也提供生成图片的方法,并且支持 await 同步,可以使用微信小程序片段代码测试一下即可。