今天,小程序正式支持 SVG


Posted in Javascript onApril 20, 2019

今天,小程序正式支持 SVG

写在前面

经过腾讯 Omi 团队的努力,今天你可以在小程序中使用 Cax 引擎高性能渲染 SVG!

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

  1. SVG 使用 XML 格式定义图形,可通过文本编辑器来创建和修改
  2. SVG 图像可被搜索、索引、脚本化或压缩
  3. SVG 是可伸缩的,且放大图片质量不下降
  4. SVG 图像可在任何的分辨率下被高质量地打印
  5. SVG 可被非常多的工具读取和修改(比如记事本)
  6. SVG 与 JPEG 和 GIF 图像比起来,尺寸更小,且可压缩性、可编程星更强
  7. 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。

那么怎么办呢?有没有办法让小程序支持 SVG? 答案是有的!需要下面这些东西(站在巨人的肩膀上):

  1. JSX,史上最强 UI 表达式,支持书写 XML-Hyperscript 互转的 JS 语言
  2. 小程序内置 Canvas 渲染器
  3. Cax 最新渲染引擎
  4. HTM,Hyperscript Tagged Markup,可能是 JSX 的替代品或者另一种选择,使用ES标准的模板语法实现的 Hyperscript 运行时/编译时生成,preact 作者(也是google工程师)打造

这里稍微解释下 Hyperscript:

比如 JSX 中的

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

或者 js 中的 htm:

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

最后都会被编译成:

h(
 "div",
 null,
 "Hello ",
 this.props.name
);

嵌套的 div 也会变编译成 h 嵌套 h,比如

<div>
 <div>abc</div>
</div>

编译后:

h(
 "div",
 null,
 h(
 "div",
 null,
 "abc"
 )
)

而 h 函数的定义也是相当简洁:

function h(type, props, ...children) {
 return { type, props, children }
}

通过 h 的执行可以 js 中拿到结构化的数据,也就是所谓的虚拟 dom。需要注意的是 htm 有轻微的运行时开销,jsx 没有。

一句话总结:

使用小程序内置的 Canvas 渲染器, 在 Cax 中实现 SVG 标准的子集,使用 JSX 或者 HTM 描述 SVG 结构行为表现

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

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"
 }
}

小程序中显示效果:

今天,小程序正式支持 SVG

可以使用 width,height,bounds-x 和 bounds-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)

小程序种显示效果:

今天,小程序正式支持 SVG

在 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"
 })
);

。

所以总结一下:

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

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

今天,小程序正式支持 SVG

详细代码:

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 老虎:

今天,小程序正式支持 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 同时变化:

今天,小程序正式支持 SVG

线性运动

这里举一个在 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)
})

效果如下:

今天,小程序正式支持 SVG

组合运动

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)
 }
})

效果如下:

今天,小程序正式支持 SVG

其他

vscode 安装 lit-html 插件使 htm 的 html内容 高亮

还希望小程序 SVG 提供什么功能可以开 issues告诉我们,评估后通过,我们去实现!

Cax SVG Github

参考文档

Javascript 相关文章推荐
jQuery 页面载入进度条实现代码
Feb 08 Javascript
javascript parseInt 函数分析(转)
Mar 21 Javascript
JQUERY 实现窗口滚动搜索框停靠效果(类似滚动停靠)
Mar 27 Javascript
给事件响应函数传参数的四种方式小结
Dec 05 Javascript
JQuery插件iScroll实现下拉刷新,滚动翻页特效
Jun 22 Javascript
基于编写jQuery的无缝滚动插件
Aug 02 Javascript
JavaScript将字符串转换成字符编码列表的方法
Mar 19 Javascript
javascript获取文档坐标和视口坐标
May 26 Javascript
JavaScript实现cookie的写入、读取、删除功能
Nov 05 Javascript
JS同步、异步、延迟加载的方法
May 05 Javascript
Vuejs中的watch实例详解(监听者)
Jan 05 Javascript
微信小程序自定义底部弹出框动画
Nov 18 Javascript
详解Vue中组件的缓存
Apr 20 #Javascript
Node.js折腾记一:读指定文件夹,输出该文件夹的文件树详解
Apr 20 #Javascript
优雅地使用loading(推荐)
Apr 20 #Javascript
详解JavaScript的数据类型以及数据类型的转换
Apr 20 #Javascript
详解key在Vue列表渲染时究竟起到了什么作用
Apr 20 #Javascript
函数式编程入门实践(一)
Apr 20 #Javascript
vue路由对不同界面进行传参及跳转的总结
Apr 20 #Javascript
You might like
php数组函数序列之array_keys() - 获取数组键名
2011/10/30 PHP
基于php实现长连接的方法与注意事项的问题
2013/05/10 PHP
PHP 登录完成后如何跳转上一访问页面
2014/01/14 PHP
php通过修改header强制图片下载的方法
2015/03/24 PHP
php cli配置文件问题分析
2015/10/15 PHP
PHP Curl模拟登录微信公众平台、新浪微博实例代码
2016/01/28 PHP
PHP array_key_exists检查键名或索引是否存在于数组中的实现方法
2016/06/13 PHP
PHP simplexml_import_dom()函数讲解
2019/02/03 PHP
javascript延时重复执行函数 lLoopRun.js
2007/06/29 Javascript
一实用的实现table排序的Javascript类库
2007/09/12 Javascript
JQuery入门——移除绑定事件unbind方法概述及应用
2013/02/05 Javascript
js给onclick事件赋值,动态传参数实例解说
2013/03/28 Javascript
整理Javascript函数学习笔记
2015/12/01 Javascript
Bootstrap table学习笔记(2) 前后端分页模糊查询
2017/05/18 Javascript
mint-ui的search组件在键盘显示搜索按钮的实现方法
2017/10/27 Javascript
AngularJS实现的简单拖拽功能示例
2018/01/02 Javascript
Vue事件处理原理及过程详解
2020/03/11 Javascript
vue.js实现双击放大预览功能
2020/06/23 Javascript
[54:47]Liquid vs VP Supermajor决赛 BO 第五场 6.10
2018/07/05 DOTA
Python比较两个图片相似度的方法
2015/03/13 Python
Python多线程编程(五):死锁的形成
2015/04/05 Python
详解Python list 与 NumPy.ndarry 切片之间的对比
2017/07/24 Python
解决python爬虫中有中文的url问题
2018/05/11 Python
Python matplotlib通过plt.scatter画空心圆标记出特定的点方法
2018/12/13 Python
理肤泉英国官网:La Roche-Posay英国
2019/01/14 全球购物
应届毕业生就业自荐信
2013/10/26 职场文书
师范生自荐信
2013/10/27 职场文书
汽车技术服务与营销专业推荐信
2013/11/29 职场文书
淘宝客服自我总结鉴定
2014/01/25 职场文书
2014年元旦活动方案
2014/02/15 职场文书
学徒工职责
2014/03/06 职场文书
病人写给医生的感谢信
2015/01/23 职场文书
预备党员转正意见
2015/06/01 职场文书
让人瞬间清醒的句子,句句经典,字字如金
2019/07/08 职场文书
go select编译期的优化处理逻辑使用场景分析
2021/06/28 Golang
CentOS 7安装mysql5.7使用XtraBackUp备份工具命令详解
2022/04/12 MySQL