Skip to content

Instantly share code, notes, and snippets.

@wulie88
Last active September 26, 2020 00:57
Show Gist options
  • Save wulie88/52ac2ba469644c65141e4d49262dc46c to your computer and use it in GitHub Desktop.
Save wulie88/52ac2ba469644c65141e4d49262dc46c to your computer and use it in GitHub Desktop.
微信小程序canvas绘制胶水代码,可用于自绘UI并截图分享,取自wxml-to-canvas组件,修复了图片圆角bug,加入了示例代码
/*
微信小程序canvas绘制胶水代码
*/
class Draw {
constructor(canvas, context) {
this.canvas = canvas
this.ctx = context
}
roundRect(x, y, w, h, r, fill = true, stroke = false) {
if (r < 0) return
const ctx = this.ctx
ctx.beginPath()
ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 3 / 2)
ctx.arc(x + w - r, y + r, r, Math.PI * 3 / 2, 0)
ctx.arc(x + w - r, y + h - r, r, 0, Math.PI / 2)
ctx.arc(x + r, y + h - r, r, Math.PI / 2, Math.PI)
ctx.lineTo(x, y + r)
if (stroke) ctx.stroke()
if (fill) ctx.fill()
}
drawView(box, style) {
const ctx = this.ctx
const {
left: x, top: y, width: w, height: h
} = box
const {
borderRadius = 0,
borderWidth = 0,
borderColor,
color = '#000',
backgroundColor = 'transparent',
} = style
ctx.save()
// 外环
if (borderWidth > 0) {
ctx.fillStyle = borderColor || color
this.roundRect(x, y, w, h, borderRadius)
}
// 内环
ctx.fillStyle = backgroundColor
const innerWidth = w - 2 * borderWidth
const innerHeight = h - 2 * borderWidth
const innerRadius = borderRadius - borderWidth >= 0 ? borderRadius - borderWidth : 0
this.roundRect(x + borderWidth, y + borderWidth, innerWidth, innerHeight, innerRadius)
ctx.restore()
}
async drawImage(img, box, style) {
await new Promise((resolve, reject) => {
const ctx = this.ctx
const canvas = this.canvas
const {
borderRadius = 0
} = style
const {
left: x, top: y, width: w, height: h
} = box
const Image = canvas.createImage()
Image.onload = () => {
ctx.save()
this.roundRect(x, y, w, h, borderRadius, false, false)
ctx.clip()
ctx.drawImage(Image, x, y, w, h)
ctx.restore()
resolve()
}
Image.onerror = () => { reject() }
Image.src = img
})
}
// eslint-disable-next-line complexity
drawText(text, box, style) {
const ctx = this.ctx
let {
left: x, top: y, width: w, height: h
} = box
let {
color = '#000',
lineHeight = '1.4em',
fontSize = 14,
textAlign = 'left',
verticalAlign = 'top',
backgroundColor = 'transparent'
} = style
if (!text || (lineHeight > h)) return
ctx.save()
if (lineHeight) { // 2em
lineHeight = Math.ceil(parseFloat(lineHeight.replace('em')) * fontSize)
}
ctx.textBaseline = 'top'
ctx.font = `${fontSize}px sans-serif`
ctx.textAlign = textAlign
// 背景色
ctx.fillStyle = backgroundColor
this.roundRect(x, y, w, h, 0)
// 文字颜色
ctx.fillStyle = color
// 水平布局
switch (textAlign) {
case 'left':
break
case 'center':
x += 0.5 * w
break
case 'right':
x += w
break
default: break
}
const textWidth = ctx.measureText(text).width
const actualHeight = Math.ceil(textWidth / w) * lineHeight
let paddingTop = Math.ceil((h - actualHeight) / 2)
if (paddingTop < 0) paddingTop = 0
// 垂直布局
switch (verticalAlign) {
case 'top':
break
case 'middle':
y += paddingTop
break
case 'bottom':
y += 2 * paddingTop
break
default: break
}
const inlinePaddingTop = Math.ceil((lineHeight - fontSize) / 2)
// 不超过一行
if (textWidth <= w) {
ctx.fillText(text, x, y + inlinePaddingTop)
return
}
// 多行文本
const chars = text.split('')
const _y = y
// 逐行绘制
let line = ''
for (const ch of chars) {
const testLine = line + ch
const testWidth = ctx.measureText(testLine).width
if (testWidth > w) {
ctx.fillText(line, x, y + inlinePaddingTop)
y += lineHeight
line = ch
if ((y + lineHeight) > (_y + h)) break
} else {
line = testLine
}
}
// 避免溢出
if ((y + lineHeight) <= (_y + h)) {
ctx.fillText(line, x, y + inlinePaddingTop)
}
ctx.restore()
}
async drawNode(element) {
const {layoutBox, computedStyle, name} = element
const {src, text} = element.attributes
if (name === 'view') {
this.drawView(layoutBox, computedStyle)
} else if (name === 'image') {
await this.drawImage(src, layoutBox, computedStyle)
} else if (name === 'text') {
this.drawText(text, layoutBox, computedStyle)
}
const childs = Object.values(element.children)
for (const child of childs) {
await this.drawNode(child)
}
}
}
export default Draw
class Node {
constructor(name, layoutBox, attributes, computedStyle) {
this.name = name
this.layoutBox = layoutBox
this.attributes = attributes
this.computedStyle = computedStyle
this.children = []
}
}
// 此处可根据类型封装,简化使用代码
export default Node
// 示例代码
// 请先引用js代码,在wxml创建canvas
Page({
data: {
src: ''
},
onReady() {
const query = wx.createSelectorQuery()
query.select('#myCanvas')
.fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node
const ctx = canvas.getContext('2d')
const dpr = wx.getSystemInfoSync().pixelRatio
canvas.width = res[0].width * dpr
canvas.height = res[0].height * dpr
ctx.scale(dpr, dpr)
const draw = new Draw(canvas, ctx)
const avatar = new Node('image', {
left: 15,
top: 18,
width: 64,
height: 64,
}, {
src: 'https://km-mall-oss.oss-cn-hangzhou.aliyuncs.com/x/ay/xayg3tz3tkzve6q1600496909.png?x-oss-process=image/resize,m_pad,h_500,w_500/quality,q_90'
}, {
borderRadius: 32
})
const username = new Node('text', {
left: 89,
top: 26,
width: 300,
height: 50,
}, {
text: 'Janmes'
}, {
color: '#000000',
fontSize: 18
})
const desc = new Node('text', {
left: 89,
top: 54,
width: 300,
height: 76,
}, {
text: '邀你一起开团,价格超级实惠'
}, {
color: '#747474',
fontSize: 14
})
const cover = new Node('image', {
left: 10,
top: 88,
width: 327,
height: 289,
}, {
src: 'https://km-mall-oss.oss-cn-hangzhou.aliyuncs.com/x/ay/xayg3tz3tkzve6q1600496909.png?x-oss-process=image/resize,m_pad,h_500,w_500/quality,q_90'
}, {
})
draw.drawNode(avatar)
draw.drawNode(username)
draw.drawNode(desc)
draw.drawNode(cover)
})
},
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment