您當前位置>首頁 » 新聞資訊 » 小(xiǎo)程序相(xiàng)關 >
微(wēi)信小(xiǎo)程序:使用(yòng)render函數(shù)在c✔★©anvas中布局生(shēng)成海(hǎi)報(bào)圖
發表時(shí)間(jiān):2021-1-5
發布人(rén):葵宇科(kē)技(jì)
浏覽次數(shù):467
一(yī)個(gè)常見(jiàn)的(de)需求,在開(kāi←ε)發微(wēi)信小(xiǎo)程序時(shí),前端需要(yào)€÷生(shēng)成海(hǎi)報(bào)圖分(fēn)享,目前常見(φ•jiàn)解決方案如(rú)下(xià):
- 使用(yòng)
htmlCanvas
庫,利用(yòng)dom來(lái)生(shēng)成圖片 - 前端使用(yòng)ctx的(de)api一(yī)個(gλ≠'₩è)一(yī)個(gè)的(de)畫(huà)出來(lái),或者借助一(yδΩπī)些(xiē)繪圖工(gōng)具
- 利用(yòng)
puppeteer
後端服務,打開(kāi)相(xiàng)應界面截圖
- 這(zhè)個(gè)庫本身(shēn)并不(bù)能(néng)在小"(xiǎo)程序使用(yòng),因為(wèi)涉及到(dào)dom,在σ£×≈web端也(yě)有(yǒu)各種兼容性問(wèn)題比如(rú)某個(gè)屬性不(±±£bù)支持
- 這(zhè)個(gè)方案,額。。。可(kě)能(néng)這(zhè)就(jiù)是( φshì)程序員(yuán)頭發少(shǎo)的(de)原因吧(ba)。費(fèi)盡千辛萬苦>✔↕畫(huà)好(hǎo),萬一(yī)視(shì)覺調整≠ 一(yī)下(xià)。。這(zhè)個(gè)方案開(kāi)發費(fèi)€>時(shí)費(fèi)力,不(bù)好(hǎo)維護。雖然web端有(yǒu)react-ca♠₹Ω£nvas,小(xiǎo)程序也(yě)有(yǒu)一(yī)些(x₽>iē)工(gōng)具,但(dàn)目前都(dōu)隻是(shì)封裝了(le)♠'$≈繪制(zhì)矩形、文(wén)字等方法,對(duì)于布局來(lá'Ωi)說(shuō)還(hái)是(shì)需要(yào)手動計(jìδ™)算(suàn)寬高(gāo)以及位置,沒有(yǒu)完全解₹π¥決痛點。
- 這(zhè)種方案對(duì)前端來(lái)說(shuō)是(shì)最完美(měi)的(d₹≥≠e),也(yě)推薦大(dà)家(jiā)有(yǒu)條件(jiàn)用(yòng>♠★)這(zhè)個(gè)方案,前端寫好(hǎo)頁面放(fàng)到(dào)服務上(shàn×→÷ g),然後再挂一(yī)個(gè)服務訪問(wèn)這(zhè)個(gè)頁面來(láα♥i)截圖,因為(wèi)開(kāi)發和(hé)截圖的(de)都(dōu)是(shì)chr>÷↕omium,基本不(bù)存在兼容性問(wèn)題。但(dàn)是(shì)這(zhè)種方案會σ (huì)非常耗費(fèi)服務器(qì)資源,每次截圖都(dō≈δ§u)要(yào)打開(kāi)一(yī)個(gè)新的(de)₩∏浏覽器(qì)tab,并且截圖耗時(shí)比較長(cháng),對(duì)于一€'(yī)些(xiē)公司來(lái)說(shuō)可(kě)→∞™↔能(néng)無法接受。
easy-canvas實現(xiàn)了(le)在canvas中創建£文(wén)檔流,api極易上(shàng)手基本沒有(yǒu)學習☆φ(xí)成本,可(kě)以很(hěn)輕松的(de)支持組件(jiàn)化(←Ω↔huà)開(kāi)發,并且沒有(yǒu)第三方依賴,隻要(yào)支持标準的(de)canva♥¶≠s就(jiù)可(kě)以使用(yòng),在實現(xiàn)¥±>★基本功能(néng)的(de)基礎上(shàng)添加了(le)事(shì)✔≤'件(jiàn)、scroll-view等支持。基礎版支持小(xiǎo)¶ ₽←程序、web。
如(rú)果使用(yòng)過render函數(shù)的(de)肯定π✘很(hěn)熟悉使用(yòng)方式了(le),相(xiàng)關屬性"<≠在項目裡(lǐ)以及示例裡(lǐ)都(dōu)有(yǒu)介紹,本篇文(w ∑↑én)章(zhāng)就(jiù)不(bù)過多(duō)介紹,基本使₽'≈用(yòng)如(rú)下(xià):
npm install easy-canvas-layγΩ×out --save
複制(zhì)代碼
import easyCanvas from 'easy-canvas-layout'
// 首先綁定圖層
const layer = easyCanvas.createL§↔¥ayer(ctx, {
dpr: 2,
width: 300,
height: 600,
canvas // 小(xiǎo)程序環境必傳
})
// 創建node
// c(tag,options,children)
const node = easyCanvas.createElement(×♥λ☆(c) => {
return c('view', {
styles: { backgroundColor:'#000' }, // 樣式
attrs:{}, // 屬性 比如(rú)src
on:{} // 事(shì)件(jiàn) 如(rú)click lα≤¥oad
},
[
c('text',{color:'#fff'},'Hello World')
])
})
// mount
node.mount(layer)
複制(zhì)代碼
vue中使用(yòng)
另外(wài)在基礎版本上(shàng),封裝了(le)相(xiàngγ↔)應的(de)vue組件(jiàn),相(xiàng)比render函數(sh®ù),要(yào)簡潔易懂(dǒng)很(hěn)多(duō),基本使用(yòng)如(rú•→≤)下(xià):
npm install vue-easy-canvas --save
複制(zhì)代碼
import easyCanvas from 'vue-easy-canvas'
Vue.use(easyCanvas)
複制(zhì)代碼
<ec-canvas :width="300" :height="600">
<ec-scroll-view :styles="{height:600}">
<ec-view :styles="styles.imageWrapper">
<ec-image
src="https://tse1-mm.cn.bing.net/th/id/OIP★&♣•.Dkj8fnK1SsPHIBmAN9XnUAHaNK?pid=Api&♦€&rs=1"
:styles="styles.image"
mode="aspectFill"></ec-image>
<ec-view :styles="styles.homeTitleWrapper">
<ec-text>easyCanvas</ec-text>
</ec-view>
</ec-view>
<ec-view :styles="styles.itemWrapper"
v-for="(item,index) in examples"
:key="index"
:on="{
click(e){
windo↕←w.location.href = http://www.wxapp-unio'₹§n.com/host + item.url
}
∑≤ }">
<ec-view :styles="styles.title">
<ec-text>{{item.title}}</ec-text>
</ec-view>
<ec-view :styles="styles.desc">
<ec-text>{{item.desc}}</ec-text>
</ec-view>
</ec-view>
</ec-scroll-view>
</ec-canvas>
複制(zhì)代碼
支持元素
-
view
基本元素,類似div -
text
文(wén)本 支持自(zì)動換行(xíng)以及超過省略等功能(néng),目前text實現(← "₽xiàn)為(wèi)inline-block -
image
圖片src
mode
支持aspectFit以及aspectFill,其他(tā)css特性同₽∞web 支持load
事(shì)件(jiàn)監聽(tīng)圖片加載并且繪制(zhì)完成 -
scroll-view
滾動容器(qì),需要(yào)在樣式裡(lǐ)設置♣•®αdirection
支持x、y、xy,并且設置具體(tǐ)尺寸 設置renderOnDemand
隻繪制(zhì)可(kě)見(jiàn)部分(fēn)
屬性使用(yòng)像素的(de)地(dì)方統一(yī)使用(yòng)¥•♣數(shù)字
-
display
block | inline-block | flex, text默認是(sh∞←≥ì)inline-block的(de) -
width
auto 100% Number 這(zhè)裡(lǐ₹≥)盒模型使用(yòng)border-box,不(bù)可(kě≠'$®)修改 -
height
-
flex
flex不(bù)支持auto,固定寬度直接使用(yòng)width -
minWidth
maxWidth
minHeight
maxHeight
如(rú)果設置了(le)具體(tǐ)寬度高(gāo)度不(bù)生( ≤®Ωshēng)效 -
margin
marginLeft
,marginRight
,marginTop
,marginBottom
margin支持數(shù)組縮寫例如(rú) [10,20][10,20≠±,10,20] -
paddingLeft
,paddingRight
,paddingTop
,paddingBottom
同上(shàng) -
backgroundColor
-
borderRadius
-
borderWidth
borderTopWidth
... 細邊框直接設置0.5 -
borderColor
-
lineHeight
字體(tǐ)相(xiàng)關的(de)隻在text內(nèi)有(yǒu)效 -
color
-
fontSize
-
textAlign
left right center -
textIndent
Number -
verticalAlign
top middle bottom -
justifyContent
flex-start center flex-end flex布局 水(shuǐ)"₹'×平方向對(duì)其 -
alignItems
flex-start center flex-end flex布局 垂直方向對™♦σ×(duì)其 -
maxLine
最大(dà)行(xíng)數(shù),超出自(zì)動省略号,隻支持在te&∏©¥xt中使用(yòng) -
whiteSpace
normal nowrap 控制(zhì)換行(xí≈£★∏ng),不(bù)能(néng)控制(zhì)字體(tǐ),φ≤隻能(néng)控制(zhì)inline-block -
overflow
hidden 如(rú)果添加了(le)圓角,會(huì)自(zì)動加上(shàn∑♦g) hidden -
flexDirection
-
borderStyle
dash Array 詳見(jiàn)ctx.set↕' LineDash() -
shadowBlur
設置了(le)陰影(yǐng)會(huì)自(zì)動加上λ(shàng) overflow:hidden; -
shadowColor
-
shadowOffsetX
-
shadowOffsetY
-
position
static
absolute
-
opacity
Number
例如(rú)這(zhè)個(gè)組件(jiàn)庫裡(lǐ)的(de)button組件(¶$<$jiàn)
正常來(lái)說(shuō)我們寫一(yī)個(gè)按鈕
.button{
display:inline-block;
background:green;
color:#fff;
font-size:14px;
padding:4px 12px;
text-align:center;
border-radius:4px;
}
複制(zhì)代碼
在easyCanvas中的(de)寫法
function Button(c){
return c('view',{
styles:{
display:'inline-block',
backgroundColor:'green',
color:'#fff',
fontSize:14,
padding:[4,12],
textAlign:'center',
borderRadius:4
}
},[
c('text',{},'按鈕')
])
}
複制(zhì)代碼
是(shì)不(bù)是(shì)覺得(de)很(hěn)熟悉很(←Ωhěn)簡單,讓我們來(lái)寫一(yī)個(gè)可(kě)以接受參數(shù)的(Ω>φde)按鈕
function Button(c, { attrs, styles, on }, content) {
const size = attrs.size || 'medium'
const nums = SIZE[size]
let _styles = Object.assign({
backgroundColor: THEME[attrs.type.toUpperCase() || 'info'],
display: 'inline-block',
borderRadius: 2,
color: '#fff',
lineHeight: nums.lineHeight,
padding: nums.padding,
fontSize: nums.fontSize
}, styles |σα₹| {})
if (attrs.plain) {
_styles.color = THEME[attr§↔β±s.type.toUpperCase()]
€ _styles.borderWidth = 0.5
_styles.borderColor = THEME[attrs.type.t©$©∞oUpperCase()]
_styles.backgroundColor = PLA§ε≤♣IN_THEME[attrs.type.toUpperCase() || 'info']
}
if (attrs.round) {
_styles£₩≈§.borderRadius = nums.borderRadius
}
return c('view', {
attrs: Object.assign({
}, attrs |>¶←¶| {}),
styles: _styles,
on: on || {},
}, typeof content === 'string' ? [c('text', {}, content)] : content)
}
複制(zhì)代碼
這(zhè)樣在使用(yòng)的(de)地(dì)方可(kě)以傳入參數(shù),像這(zh蕱φ)樣,也(yě)就(jiù)是(shì)大(dà)家(jiā)在demo裡(lǐ)÷®δ看(kàn)到(dào)的(de)
Button(c, {
attrs: { type: 'primary', plain: true },
}, '主要(yào)按鈕'),
Button(c, {
attrs: { type: 'success', plain: true },
}, '成功按鈕'),
Button(c, {
attrs: { type: 'info', plain: true },
}, '信息按鈕'),
Button(c, {
attrs: { type: 'warning', plain: true },
}, '警告按鈕'),
Button(c, {
attrs: { type: 'error', plain: true },
}, '危險按鈕'),
複制(zhì)代碼
并且,easyCanvas支持注冊全局組件(jiàn),方便調用(yòng)• •←,其他(tā)參數(shù)請(qǐng)看(kàn)項₽φ <目使用(yòng)文(wén)檔
// 注冊全局組件(jiàn)
easyCanvas.component('button',Button)
// 使用(yòng)全局組件(jiàn)
function Page(c){
return c('button',{
attrs: { type: 'warning', plain: true },
}, '警告按鈕')
}
複制(zhì)代碼
另外(wài)easyCanvas內(nèi)置了(le)事(shì)件(jiàn)管理(∏$lǐ)器(qì),可(kě)以支持類似web中的(de)事(shì)件(jià↕↓€n),從(cóng)父級向子(zǐ)級執行(xíng)捕獲,子(zǐ)級♥↑φΩ再向父級冒泡。
首先需要(yào)讓canvas元素接管事(shì)件(jiàn)
// canvas元素監聽(tīng)鼠标事(shì)件(jiàn)
canvas.ontouchstart = ontouchstart
canva> ↕αs.ontouchmove = ontouchmove
canvas.ontoucσ ™hend = ontouchend
canvas.onmγ× £ousedown = ontouchstart
canvas.onmΩ©♠ousemove = ontouchmove
canvas.onmo÷useup = ontouchend
canvas.onmousewheelβ✔∏γ = onmousewheel
// 将事(shì)件(jiàn)交給事(shì)件®←←✘(jiàn)管理(lǐ)器(qì)接管 需要(yào)注意的(de)是(shì),↑×≤•這(zhè)裡(lǐ)的(de)坐(zuò)标是(shì)相(xiàng)對(≥₩≠×duì)于canvas元素的(de)坐(zuò)标,而不(bù)是(shì)屏幕
function ontouchstart(e) {
e.preventDefault()
layer.eventManager.tou ©σchstart(e.pageX || e.touches≥₩ [0].pageX || 0, e.pageY || e.touches[0].pageY || 0)
}
function ontouchmove(e) {
e.preventDefault()
₽→layer.eventManager.touchmove(e.pageX÷α÷↓ || e.touches[0].pageX || 0, e.pageY || e.touches[0].pageY || 0)
}
function ontouchend(e) {
e.preventDefault()
la>'≤εyer.eventManager.touchend(
e.pageX || e.ch↕>±✔angedTouches[0].pageX || 0,
e.pageY || e.changedTouch↕®es[0].pageY || 0
)
}
function onClick(e) {
e.preventDefault()
laye≤εr.eventManager.click(e.pageX, e.pageY↕₹ ↕)
}
function onmousewheel(e){
e.preventDefault()
lπφayer.eventManager.mousewheel(e.pageX,e.pageY,-e.∑→<♦deltaX,-e.deltaY)
}
複制(zhì)代碼
接管到(dào)事(shì)件(jiàn)後,我們就(ji✔∏ù)可(kě)以在元素內(nèi)監聽(tīng)事(shì)件(π¥jiàn)了(le)
c('button',{
id:'測試按鈕',
on:{
click(e){
// 阻止冒泡到(dào)父級
e.stopPropagation()
$π alert(e.currentTarget.id) // alert 測試按鈕
}
}
},'點我點我')
複制(zhì)代碼
目前支持的(de)鼠标事(shì)件(jiàn)有(yǒu): click、touch•₹ start、touchmove、touchend、mou →↕sewheel。
圖片支持 load、error事(shì)件(jiàn)
另外(wài),支持在layer中監聽(tīng)所有(yǒu)圖片請(qǐng)求完成,比如←">(rú)我們需要(yào)在圖片加載完成,reflow布局并且重新渲染後立即生(shēng)成圖片✘λ:
easyCanvas.createLayer(ctx, {
ε'©γ dpr,
width,
height,
lifecycle: {
onEffectSuccess(res) {
// 所有(yǒu)圖片加載成功
},
onEffectFail(res) {
// 有(yǒu)圖片加載失敗
},
onEffectComplet↓β•e(){
// 隻要(yào)加載結束就(jiù)會(huì)調用(yòng)
// 生(shēng)成圖片...
}
}
})
複制(zhì)代碼
easyCanvas還(hái)支持在初始渲染後對(duì)元素進行(xíng)操作(zuò)
// 獲取元素 key為(wèi)attrs中定義的(de)
el.getElementBy(key,value)
// 增加元素
el.appendChild(element)
el.prependCh×♦→ild(element)
el.append(element) // 加在當前元素後
el.prepend(element)
// 删除元素
el.removeChild(element)
el.remove(γ®)
// 修改樣式 內(nèi)部會(huì)根據樣式判斷是(shì)否需要(yào)rβΩeflow還(hái)是(shì)僅僅repaint就(jiù)足夠
el.setStyles(styles)
複制(zhì)代碼
demo中點擊左側右側定位代碼
c('view', {
on: {
click(e) {
const target = layer.getElementBy✘£☆ ('id', item.en)[0]
if (!target || e.currentTarget ===¥₽× lastSelect) return
const scrollView = layer.getElementBy('id', 'main')[1]
scrollView.scrollTo({ y: target.y })
e.c≠☆≤urrentTarget.setStyles({ backgroundColor: '#f1f1f1', color: '#333' })
if (lastSelect) lastSelect.se≠↕$™tStyles({ backgroundColor: '' })
lastSelect = e.cu✔ε₽rrentTarget
}
},
styles: {
padding: 10,
color: '#666',
fontSize: 16
}
}, [c('text', {}, item.en + ' ' + item.zh)]))
複制(zhì)代碼
Ending
本篇文(wén)章(zhāng)主要(yào)介紹項目背景以及基本使用(yòng),也(yě)是≥✔¥(shì)為(wèi)了(le)給自(zì)己打個(gè)廣告吧(ba):) 後面會≠ ↑(huì)寫實現(xiàn)原理(lǐ)以及一(yī)些(xiē)坑,歡≥'π迎各位交流,感謝(xiè)閱讀(dú)!
作(zuò)者:FiyN
來(lái)源:掘金(jīn)
著作(zuò)權歸作(zuò)者所有(yǒu)。商業(yè)轉載請(qǐng)聯系δ♠作(zuò)者獲得(de)授權,非商業(yè)轉載請(qǐng)注₩♣明(míng)出處。