评论

用 HTM 实现小程序 SVG

今天你可以在小程序中使用 Cax 引擎高性能渲染 SVG!

写在前面

今天你可以在小程序中使用 Cax 引擎高性能渲染 SVG!

SVG 是可缩放矢量图形(Scalable Vector Graphics),基于可扩展标记语言,用于描述二维矢量图形的一种图形格式。它由万维网联盟制定,是一个开放标准。SVG 的优势有很多:

  • SVG 使用 XML 格式定义图形,可通过文本编辑器来创建和修改
  • SVG 图像可被搜索、索引、脚本化或压缩
  • SVG 是可伸缩的,且放大图片质量不下降
  • SVG 图像可在任何的分辨率下被高质量地打印
  • SVG 可被非常多的工具读取和修改(比如记事本)
  • SVG 与 JPEG 和 GIF 图像比起来,尺寸更小,且可压缩性、可编程星更强
  • SVG 完全支持 DOM 编程,具有交互性和动态性

而支持上面这些优秀特性的前提是 - 需要支持 SVG 标签。比如在小程序中直接写:

<svg width="300" height="150">
  <rect
    bindtap="tapHandler" height="100" width="100"
    style="stroke:#ff0000; fill: #0000ff">
  </rect>
</svg>

上面定义了 SVG 的结构、样式和点击行为。但是小程序目前不支持 SVG 标签,仅仅支持加载 SVG 之后 作为 background-image 进行展示,如 background-image: url("data:image/svg+xml.......),或者 base64 后作为 background-image 的 url。

直接看在小程序种使用案例:

import { html, renderSVG } from '../../cax/cax'

Page({
  onLoad: function () {

    renderSVG(html`
<svg width="300" height="220">
  <rect bindtap="tapHandler"
  height="110" width="110"
  style="stroke:#ff0000; fill: #ccccff"
  transform="translate(100 50) rotate(45 50 50)">
  </rect>
</svg>`, 'svg-a', this)

  },

  tapHandler: function () {
    console.log('你点击了 rect')
  }
})

其中的 svg-a 对应着 wxml 里 cax-element 的 id:

<view class="container">
  <cax-element id="svg-c"></cax-element>
</view>

声明组件依赖

{
  "usingComponents": {
    "cax-element":"../../cax/index"
  }
}

小程序中显示效果:

可以使用 widthheightbounds-xbounds-y 设置绑定事件的范围,比如:

<path width="100" height="100" bounds-x="50" bounds-y="50" />

需要注意的是,元素的事件触发的包围盒受自身或者父节点的 transform 影响,所以不是绝对坐标的 rect 触发区域。

再来一个复杂的例子,用 SVG 绘制 Omi 的 logo:

renderSVG(html`
<svg width="300" height="220">
  <g transform="translate(50,10) scale(0.2 0.2)">
   <circle fill="#07C160" cx="512" cy="512" r="512"/>
   <polygon fill="white" points="159.97,807.8 338.71,532.42 509.9,829.62 519.41,829.62 678.85,536.47 864.03,807.8 739.83,194.38 729.2,194.38 517.73,581.23 293.54,194.38 283.33,194.38 "/>
   <circle fill="white" cx="839.36" cy="242.47" r="50"/>
  </g>
</svg>`, 'svg-a', this)

小程序种显示效果:

在 omip 和 mps 当中使用 cax 渲染 svg,你可以不用使用 htm。比如在 omip 中实现上面两个例子:

    renderSVG(
<svg width="300" height="220">
  <rect bindtap="tapHandler"
  height="110" width="110"
  style="stroke:#ff0000; fill: #ccccff"
  transform="translate(100 50) rotate(45 50 50)">
  </rect>
</svg>, 'svg-a', this.$scope)
renderSVG(
<svg width="300" height="220">
  <g transform="translate(50,10) scale(0.2 0.2)">
   <circle fill="#07C160" cx="512" cy="512" r="512"/>
   <polygon fill="white" points="159.97,807.8 338.71,532.42 509.9,829.62 519.41,829.62 678.85,536.47 864.03,807.8 739.83,194.38 729.2,194.38 517.73,581.23 293.54,194.38 283.33,194.38 "/>
   <circle fill="white" cx="839.36" cy="242.47" r="50"/>
  </g>
</svg>, 'svg-a', this.$scope)

需要注意的是在 omip 中传递的最后一个参数不是 this,而是 this.$scope

在 mps 中,更加彻底,你可以单独创建 svg 文件,通过 import 导入。

//注意这里不能写 test.svg,因为 mps 会把 test.svg 编译成 test.js 
import testSVG from '../../svg/test'
import { renderSVG } from '../../cax/cax'

Page({
  tapHandler: function(){
    this.pause = !this.pause
  },
  onLoad: function () {
    renderSVG(testSVG, 'svg-a', this)
  }
})

比如 test.svg :

<svg width="300" height="300">
  <rect bindtap="tapHandler" x="0" y="0" height="110" width="110"
         style="stroke:#ff0000; fill: #0000ff" />
</svg>

会被 mps 编译成:

const h = (type, props, ...children) => ({ type, props, children });
export default h(
  "svg",
  { width: "300", height: "300" },
  h("rect", {
    bindtap: "tapHandler",
    x: "0",
    y: "0",
    height: "110",
    width: "110",
    style: "stroke:#ff0000; fill: #0000ff"
  })
);

所以总结一下:

  • 你可以在 mps 中直接使用 import 的 SVG 文件的方式使用 SVG
  • 你可以直接在 omip 中使用 JSX 的使用 SVG
  • 你可以直接在原生小程序当中使用 htm 的方式使用 SVG

这就完了?远没有,看 cax 在小程序中的这个例子:

详细代码:

renderSVG(html`
<svg width="300" height="200">
  <path d="M 256,213 C 245,181 206,187 234,262 147,181 169,71.2 233,18 220,56 235,81 283,88 285,78.7 286,69.3 288,60 289,61.3 290,62.7 291,64 291,64 297,63 300,63 303,63 309,64 309,64 310,62.7 311,61.3 312,60 314,69.3 315,78.7 317,88 365,82 380,56 367,18 431,71 453,181 366,262 394,187 356,181 344,213 328,185 309,184 300,284 291,184 272,185 256,213 Z" style="stroke:#ff0000; fill: black">
    <animate dur="32s" repeatCount="indefinite" attributeName="d" values="......太长,这里省略 paths........" />
  </path>
</svg>`, 'svg-c', this)

再试试著名的 SVG 老虎:

path 太长,就不贴代码了,可以点击这里查看

pasiton 标签

import { html, renderSVG } from '../../cax/cax'

Page({
  onLoad: function () {


    const svg = renderSVG(html`
<svg width="200" height="200">
  <pasition duration="200" bindtap=${this.changePath} width="100" height="100" from="M28.228,23.986L47.092,5.122c1.172-1.171,1.172-3.071,0-4.242c-1.172-1.172-3.07-1.172-4.242,0L23.986,19.744L5.121,0.88
		c-1.172-1.172-3.07-1.172-4.242,0c-1.172,1.171-1.172,3.071,0,4.242l18.865,18.864L0.879,42.85c-1.172,1.171-1.172,3.071,0,4.242
		C1.465,47.677,2.233,47.97,3,47.97s1.535-0.293,2.121-0.879l18.865-18.864L42.85,47.091c0.586,0.586,1.354,0.879,2.121,0.879
		s1.535-0.293,2.121-0.879c1.172-1.171,1.172-3.071,0-4.242L28.228,23.986z"
    to="M49.1 23.5H2.1C0.9 23.5 0 24.5 0 25.6s0.9 2.1 2.1 2.1h47c1.1 0 2.1-0.9 2.1-2.1C51.2 24.5 50.3 23.5 49.1 23.5zM49.1 7.8H2.1C0.9 7.8 0 8.8 0 9.9c0 1.1 0.9 2.1 2.1 2.1h47c1.1 0 2.1-0.9 2.1-2.1C51.2 8.8 50.3 7.8 49.1 7.8zM49.1 39.2H2.1C0.9 39.2 0 40.1 0 41.3s0.9 2.1 2.1 2.1h47c1.1 0 2.1-0.9 2.1-2.1S50.3 39.2 49.1 39.2z"
    from-stroke="red" to-stroke="green" from-fill="blue" to-fill="red" stroke-width="2" />
</svg>`, 'svg-c', this)

    this.pasitionElement = svg.children[0]

  },

  changePath: function () {
    this.pasitionElement.toggle()
  }
})

pasiton 提供了两个 path 和 颜色 相互切换的能力,最常见的场景比如 menu 按钮和 close 按钮点击后 path 的变形。

举个例子,看颜色和 path 同时变化:

线性运动

这里举一个在 mps 中使用 SVG 的案例:

import { renderSVG, To } from '../../cax/cax'

Page({
  tapHandler: function(){
    this.pause = !this.pause
  },

  onLoad: function () {
    const svg = renderSVG(html`
    <svg width="300" height="300">
     <rect bindtap="tapHandler" x="0" y="0" height="110" width="110"
            style="stroke:#ff0000; fill: #0000ff" />
    </svg>`
    , 'svg-a', this)
    const rect = svg.children[0]
    rect.originX = rect.width/2
    rect.originY = rect.height/2
    rect.x = svg.stage.width/2
    rect.y = svg.stage.height/2
    this.pause = false
    this.interval = setInterval(()=>{
      if(!this.pause){
        rect.rotation++
        svg.stage.update()
      }
    },15)
})

效果如下:

组合运动

import { renderSVG, To } from '../../cax/cax'

Page({
  onLoad: function () {

    const svg = renderSVG(html`
    <svg width="300" height="300">
     <rect bindtap="tapHandler" x="0" y="0" height="110" width="110"
            style="stroke:#ff0000; fill: #0000ff" />
    </svg>`
    ,'svg-a', this)
    const rect = svg.children[0]
    rect.originX = rect.width/2
    rect.originY = rect.height
    rect.x = svg.stage.width/2
    rect.y = svg.stage.height/2

    var sineInOut = To.easing.sinusoidalInOut
    To.get(rect)
        .to().scaleY(0.8, 450, sineInOut).skewX(20, 900, sineInOut)
        .wait(900)
        .cycle().start()
    To.get(rect)
        .wait(450)
        .to().scaleY(1, 450, sineInOut)
        .wait(900)
        .cycle().start()
    To.get(rect)
        .wait(900)
        .to().scaleY(0.8, 450, sineInOut).skewX(-20, 900, sineInOut)
        .cycle()
        .start()
    To.get(rect)
        .wait(1350)
        .to().scaleY(1, 450, sineInOut)
        .cycle()
        .start()

      setInterval(() => {
          rect.stage.update()
      }, 16)
  }
})

效果如下:

其他

  • vscode 安装 lit-html 插件使 htm 的 html内容 高亮
  • 还希望小程序 SVG 提供什么功能可以开 issues告诉我们,评估后通过,我们去实现!
  • Cax Github
  • 参考文档
最后一次编辑于  01-04  
点赞 30
收藏
评论

33 个评论

  • cookie
    cookie
    2023-03-03

    为什么著名的老虎图,用canvas不能完全还原?

    const handDrawPath = (nodeData) => {
        const p = nodeData.d && new Path2D(nodeData.d)
        if(!isNaN(nodeData.strokeWidth)){
          canvasContent.lineWidth = nodeData.strokeWidth;
        }
    
    
        if(nodeData.stroke){
         canvasContent.strokeStyle =  nodeData.stroke;
         canvasContent.stroke(p)
        } else {
          canvasContent.strokeStyle = null
        }
    
    
        if(nodeData.fill) {
          canvasContent.fillStyle = nodeData.fill;
          canvasContent.fill(p)
        }else {
          canvasContent.fillStyle = null
        }
      }
    
    
    2023-03-03
    赞同
    回复
  • 小太阳
    小太阳
    2022-12-08

    文章中cax包相关链接失效了https://github.com/Tencent/omi/tree/master/packages/cax/cax ,求问大佬哪里能找到

    2022-12-08
    赞同
    回复 2
    • 可乐加冰
      可乐加冰
      2023-01-10
      对啊页面找不到了,你问题解决了么
      2023-01-10
      回复
    • 曲建国
      曲建国
      2023-03-10
      应该已经没人维护了吧,这篇文章中的东西都已经失效了
      2023-03-10
      回复
  • 阿亮
    阿亮
    2022-06-12

    这货不支持path点击事件,好像cax没有人维护的样子

    2022-06-12
    赞同
    回复
  • 关
    2021-01-27

    真是无奈了,看完决定不使用svg了

    2021-01-27
    赞同
    回复
  • CC
    CC
    2020-11-25

    canvas层级的原因导致前端组件没办法很好使用,所以才想着用svg啊... 结果svg也是假的吗

    2020-11-25
    赞同
    回复
  • 狗头人
    狗头人
    2020-10-31

    请问楼主知不知道如何把svg文件上传到公众号的图床啊

    2020-10-31
    赞同
    回复
  • 元宝@熬
    元宝@熬
    2020-05-14

    正在做小程序svg开发,求这方便的技术群

    2020-05-14
    赞同
    回复
  • 俊涛🏸
    俊涛🏸
    2019-10-23

    >>>其中的 svg-a 对应着 wxml 里 cax-element 的 id:

    >>><view class="container">  <cax-element id="svg-c"></cax-element></view>

    文档给的例子中,id写成了svg-c,可能会造成尝试启动代码时,报错,请注意应该是svg-a

    <view class="container">  <cax-element id="svg-a"></cax-element></view>

    2019-10-23
    赞同
    回复 1
    • Lorenzo
      Lorenzo
      2021-05-30
      我也发现了,提示setData of null,一脸懵逼。真的,看了一下这个cax的目录结构,对这个一点信心都没有。
      2021-05-30
      回复
  • 2019-08-02

    renderSVG(<svg width="300" height="220">  <rect bindtap="tapHandler"  height="110" width="110"  style="stroke:#ff0000; fill: #ccccff"  transform="translate(100 50) rotate(45 50 50)">  </rect></svg>, 'svg-a', this.$scope)

    这代码放程序里明显出错

    <svg width="300" height="220">  <rect bindtap="tapHandler"  height="110" width="110"  style="stroke:#ff0000; fill: #ccccff"  transform="translate(100 50) rotate(45 50 50)">  </rect></svg>

    这段也不算字符串啊

    2019-08-02
    赞同
    回复 1
  • 2019-08-02

    html`<div>  Hello {this.props.name} </div>`

    请问我定义字符串var str1="<div>Hello {this.props.name}</div>";

    楼主给的这个代码应该怎么变?html`${str1}`

    怎么试都不对


    2019-08-02
    赞同
    回复 2
    • dntzhang(张磊)
      dntzhang(张磊)
      2019-08-09
      不能使用 html`${str1}`
      2019-08-09
      回复
    • 英姿
      英姿
      2020-06-22回复dntzhang(张磊)
      请问如果要引入外部svg时,该如何操作?
      2020-06-22
      回复

正在加载...

登录 后发表内容