Last active
September 26, 2020 00:57
-
-
Save wulie88/52ac2ba469644c65141e4d49262dc46c to your computer and use it in GitHub Desktop.
微信小程序canvas绘制胶水代码,可用于自绘UI并截图分享,取自wxml-to-canvas组件,修复了图片圆角bug,加入了示例代码
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
微信小程序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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Node { | |
constructor(name, layoutBox, attributes, computedStyle) { | |
this.name = name | |
this.layoutBox = layoutBox | |
this.attributes = attributes | |
this.computedStyle = computedStyle | |
this.children = [] | |
} | |
} | |
// 此处可根据类型封装,简化使用代码 | |
export default Node |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 示例代码 | |
// 请先引用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