起因:非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 同步,可以使用微信小程序片段代码测试一下即可。