微信小程序:使用render函數在(zài)canvas中布局生成海報圖 - 新聞資訊 - 雲南小程序開發|雲南軟件開發|雲南網站建設-昆明融晨信息技術有限公司

159-8711-8523

雲南網建設/小程序開發/軟件開發

知識

不(bù)管是(shì)網站,軟件還是(shì)小程序,都要(yào / yāo)直接或間接能爲(wéi / wèi)您産生價值,我們在(zài)追求其視覺表現的(de)同時(shí),更側重于(yú)功能的(de)便捷,營銷的(de)便利,運營的(de)高效,讓網站成爲(wéi / wèi)營銷工具,讓軟件能切實提升企業内部管理水平和(hé / huò)效率。優秀的(de)程序爲(wéi / wèi)後期升級提供便捷的(de)支持!

您當前位置>首頁 » 新聞資訊 » 小程序相關 >

微信小程序:使用render函數在(zài)canvas中布局生成海報圖

發表時(shí)間:2021-1-5

發布人(rén):融晨科技

浏覽次數:454

背景

一個(gè)常見的(de)需求,在(zài)開發微信小程序時(shí),前端需要(yào / yāo)生成海報圖分享,目前常見解決方案如下:

  1. 使用htmlCanvas庫,利用dom來(lái)生成圖片
  2. 前端使用ctx的(de)api一個(gè)一個(gè)的(de)畫出(chū)來(lái),或者借助一些繪圖工具
  3. 利用puppeteer後端服務,打開相應界面截圖
痛點:
  1. 這(zhè)個(gè)庫本身并不(bù)能在(zài)小程序使用,因爲(wéi / wèi)涉及到(dào)dom,在(zài)web端也(yě)有各種兼容性問題比如某個(gè)屬性不(bù)支持
  2. 這(zhè)個(gè)方案,額。。。可能這(zhè)就(jiù)是(shì)程序員頭發少的(de)原因吧。費盡千辛萬苦畫好,萬一視覺調整一下。。這(zhè)個(gè)方案開發費時(shí)費力,不(bù)好維護。雖然web端有react-canvas,小程序也(yě)有一些工具,但目前都隻是(shì)封裝了(le/liǎo)繪制矩形、文字等方法,對于(yú)布局來(lái)說(shuō)還是(shì)需要(yào / yāo)手動計算寬高以(yǐ)及位置,沒有完全解決痛點。
  3. 這(zhè)種方案對前端來(lái)說(shuō)是(shì)最完美的(de),也(yě)推薦大(dà)家有條件用這(zhè)個(gè)方案,前端寫好頁面放到(dào)服務上(shàng),然後再挂一個(gè)服務訪問這(zhè)個(gè)頁面來(lái)截圖,因爲(wéi / wèi)開發和(hé / huò)截圖的(de)都是(shì)chromium,基本不(bù)存在(zài)兼容性問題。但是(shì)這(zhè)種方案會非常耗費服務器資源,每次截圖都要(yào / yāo)打開一個(gè)新的(de)浏覽器tab,并且截圖耗時(shí)比較長,對于(yú)一些公司來(lái)說(shuō)可能無法接受。
簡介

easy-canvas實現了(le/liǎo)在(zài)canvas中創建文檔流,api極易上(shàng)手基本沒有學習成本,可以(yǐ)很輕松的(de)支持組件化開發,并且沒有第三方依賴,隻要(yào / yāo)支持标準的(de)canvas就(jiù)可以(yǐ)使用,在(zài)實現基本功能的(de)基礎上(shàng)添加了(le/liǎo)事件、scroll-view等支持。基礎版支持小程序、web。

如果使用過render函數的(de)肯定很熟悉使用方式了(le/liǎo),相關屬性在(zài)項目裏以(yǐ)及示例裏都有介紹,本篇文章就(jiù)不(bù)過多介紹,基本使用如下:

npm install easy-canvas-layout --save
複制代碼
    import easyCanvas from 'easy-canvas-layout'

    // 首先綁定圖層
    const layer = easyCanvas.createLayer(ctx, {
      dpr: 2,
      width: 300,
      height: 600,
      canvas   // 小程序環境必傳
    })

    // 創建node 
    // c(tag,options,children)
    const node = easyCanvas.createElement((c) => {
      return c('view', { 
        styles: { backgroundColor:'#000' }, // 樣式
        attrs:{},                           // 屬性 比如src
        on:{}                               // 事件 如click load 
      }, 
      [
        c('text',{color:'#fff'},'Hello World')
      ])
    })

    // mount
    node.mount(layer)

複制代碼
vue中使用

另外在(zài)基礎版本上(shàng),封裝了(le/liǎo)相應的(de)vue組件,相比render函數,要(yào / yāo)簡潔易懂很多,基本使用如下:

npm install vue-easy-canvas --save
複制代碼
import easyCanvas from 'vue-easy-canvas'
Vue.use(easyCanvas)
複制代碼
<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){
            window.location.href = http://www.wxapp-union.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>
複制代碼
支持元素
  • view 基本元素,類似div
  • text 文本 支持自動換行以(yǐ)及超過省略等功能,目前text實現爲(wéi / wèi)inline-block
  • image 圖片 src mode支持aspectFit以(yǐ)及aspectFill,其他(tā)css特性同web 支持load事件監聽圖片加載并且繪制完成
  • scroll-view 滾動容器,需要(yào / yāo)在(zài)樣式裏設置direction 支持x、y、xy,并且設置具體尺寸 設置renderOnDemand隻繪制可見部分
支持屬性

屬性使用像素的(de)地(dì / de)方統一使用數字

  • display block | inline-block | flex, text默認是(shì)inline-block的(de)
  • width auto 100% Number 這(zhè)裏盒模型使用border-box,不(bù)可修改
  • height
  • flex flex不(bù)支持auto,固定寬度直接使用width
  • minWidth maxWidth minHeight maxHeight 如果設置了(le/liǎo)具體寬度高度不(bù)生效
  • margin marginLeft,marginRight,marginTop,marginBottom margin支持數組縮寫例如 [10,20][10,20,10,20]
  • paddingLeft,paddingRight,paddingTop,paddingBottom 同上(shàng)
  • backgroundColor
  • borderRadius
  • borderWidth borderTopWidth ... 細邊框直接設置0.5
  • borderColor
  • lineHeight 字體相關的(de)隻在(zài)text内有效
  • color
  • fontSize
  • textAlign left right center
  • textIndent Number
  • verticalAlign top middle bottom
  • justifyContent flex-start center flex-end flex布局 水平方向對其
  • alignItems flex-start center flex-end flex布局 垂直方向對其
  • maxLine 最大(dà)行數,超出(chū)自動省略号,隻支持在(zài)text中使用
  • whiteSpace normal nowrap 控制換行,不(bù)能控制字體,隻能控制inline-block
  • overflow hidden 如果添加了(le/liǎo)圓角,會自動加上(shàng) hidden
  • flexDirection
  • borderStyle dash Array 詳見ctx.setLineDash()
  • shadowBlur 設置了(le/liǎo)陰影會自動加上(shàng) overflow:hidden;
  • shadowColor
  • shadowOffsetX
  • shadowOffsetY
  • position static absolute
  • opacity Number

例如這(zhè)個(gè)組件庫裏的(de)button組件

正常來(lái)說(shuō)我們寫一個(gè)按鈕

.button{
    display:inline-block;
    background:green;
    color:#fff;
    font-size:14px;
    padding:4px 12px;
    text-align:center;
    border-radius:4px;
}
複制代碼

在(zài)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',{},'按鈕')
    ])
}
複制代碼

是(shì)不(bù)是(shì)覺得很熟悉很簡單,讓我們來(lái)寫一個(gè)可以(yǐ)接受參數的(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[attrs.type.toUpperCase()]
    _styles.borderWidth = 0.5
    _styles.borderColor = THEME[attrs.type.toUpperCase()]
    _styles.backgroundColor = PLAIN_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è)樣在(zài)使用的(de)地(dì / de)方可以(yǐ)傳入參數,像這(zhè)樣,也(yě)就(jiù)是(shì)大(dà)家在(zài)demo裏看到(dào)的(de)

Button(c, {
    attrs: { type: 'primary', plain: true },
}, '主要(yào / 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 },
}, '危險按鈕'),
複制代碼

并且,easyCanvas支持注冊全局組件,方便調用,其他(tā)參數請看項目使用文檔

// 注冊全局組件
easyCanvas.component('button',Button)

// 使用全局組件
function Page(c){
    return c('button',{
        attrs: { type: 'warning', plain: true },
    }, '警告按鈕')
}
複制代碼

另外easyCanvas内置了(le/liǎo)事件管理器,可以(yǐ)支持類似web中的(de)事件,從父級向子(zǐ)級執行捕獲,子(zǐ)級再向父級冒泡。

首先需要(yào / yāo)讓canvas元素接管事件


// canvas元素監聽鼠标事件
canvas.ontouchstart = ontouchstart
canvas.ontouchmove = ontouchmove
canvas.ontouchend = ontouchend
canvas.onmousedown = ontouchstart
canvas.onmousemove = ontouchmove
canvas.onmouseup = ontouchend
canvas.onmousewheel = onmousewheel


// 将事件交給事件管理器接管 需要(yào / yāo)注意的(de)是(shì),這(zhè)裏的(de)坐标是(shì)相對于(yú)canvas元素的(de)坐标,而(ér)不(bù)是(shì)屏幕
function ontouchstart(e) {
  e.preventDefault()
  layer.eventManager.touchstart(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()
  layer.eventManager.touchend(
    e.pageX || e.changedTouches[0].pageX || 0,
    e.pageY || e.changedTouches[0].pageY || 0
  )
}
function onClick(e) {
  e.preventDefault()
  layer.eventManager.click(e.pageX, e.pageY)
}
function onmousewheel(e){
  e.preventDefault()
  layer.eventManager.mousewheel(e.pageX,e.pageY,-e.deltaX,-e.deltaY)
}
複制代碼

接管到(dào)事件後,我們就(jiù)可以(yǐ)在(zài)元素内監聽事件了(le/liǎo)

c('button',{
    id:'測試按鈕',
    on:{
        click(e){
            // 阻止冒泡到(dào)父級
            e.stopPropagation()
            alert(e.currentTarget.id) // alert 測試按鈕
        }
    }
},'點我點我')
複制代碼

目前支持的(de)鼠标事件有: click、touchstart、touchmove、touchend、mousewheel。

圖片支持 load、error事件

另外,支持在(zài)layer中監聽所有圖片請求完成,比如我們需要(yào / yāo)在(zài)圖片加載完成,reflow布局并且重新渲染後立即生成圖片:

easyCanvas.createLayer(ctx, {
    dpr,
    width,
    height,
    lifecycle: {
        onEffectSuccess(res) {
            // 所有圖片加載成功
        },
        onEffectFail(res) {
            // 有圖片加載失敗
        },
        onEffectComplete(){
            // 隻要(yào / yāo)加載結束就(jiù)會調用
            // 生成圖片...
        }
    }
})
複制代碼

easyCanvas還支持在(zài)初始渲染後對元素進行操作

// 獲取元素 key爲(wéi / wèi)attrs中定義的(de)
el.getElementBy(key,value)

// 增加元素
el.appendChild(element)
el.prependChild(element)
el.append(element) // 加在(zài)當前元素後
el.prepend(element)

// 删除元素
el.removeChild(element)
el.remove()

// 修改樣式 内部會根據樣式判斷是(shì)否需要(yào / yāo)reflow還是(shì)僅僅repaint就(jiù)足夠
el.setStyles(styles)
複制代碼

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.currentTarget.setStyles({ backgroundColor: '#f1f1f1', color: '#333' })
            if (lastSelect) lastSelect.setStyles({ backgroundColor: '' })
            lastSelect = e.currentTarget
        }
    },
    styles: {
        padding: 10,
        color: '#666',
        fontSize: 16
    }
}, [c('text', {}, item.en + ' ' + item.zh)]))
複制代碼
Ending

本篇文章主要(yào / yāo)介紹項目背景以(yǐ)及基本使用,也(yě)是(shì)爲(wéi / wèi)了(le/liǎo)給自己打個(gè)廣告吧:) 後面會寫實現原理以(yǐ)及一些坑,歡迎各位交流,感謝閱讀!


作者:FiyN
來(lái)源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出(chū)處。

相關案例查看更多