网页程序迁移至微信小程序web,明白SVG坐标系和

网页程序迁移至微信小程序web-view详解

2018/08/02 · JavaScript · 小程序

原文出处: NeoPasser   

小程序现在越来越流行,但是公司的很多项目都是用网页写的,小程序语法不兼容原生网页,使得旧有项目迁移至小程序代价很高。

小程序之前开放了webview功能,可以说是网页应用的一大福音了,但是微信的webview有一些坑,这篇文章就是列举一下我在开发过程中遇到的一些问题以及我找到的一些解决方案。

谈谈 HTTP/2 的协议协商机制

2016/04/16 · 基础技术 · HTTP/2

本文作者: 伯乐在线 - JerryQu 。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

文章目录

  • HTTP Upgrade
  • ALPN 扩展
  • 小结

在过去的几个月里,我写了很多有关 HTTP/2 的文章,也做过好几场相关分享。我在向大家介绍 HTTP/2 的过程中,有一些问题经常会被问到。例如要部署 HTTP/2 一定要先升级到 HTTPS 么?升级到 HTTP/2 之后,不支持 HTTP/2 的浏览器还能正常访问么?本文重点介绍 HTTP/2 的协商机制,明白了服务端和客户端如何协商出最终使用的 HTTP 协议版本,这两个问题就迎刃而解了。

理解SVG坐标系和变换:视窗,viewBox和preserveAspectRatio

2015/09/23 · HTML5 · SVG

原文出处: SaraSoueidan   译文出处:Blueed(@Ivan_z3)   

SVG元素不像HTML元素一样由CSS盒模型管理。这使得我们可以更加灵活定位和变换这些元素-也许一眼看上去不太直观。然而,一旦你理解了SVG坐标系和变换,操纵SVG会非常简单并且很有意义。本篇文章中我们将讨论控制SVG坐标系的最重要的三个属性:viewport, viewBox, 和 preserveAspectRatio

这是本系列三篇文章中的第一篇,这篇文章讨论SVG中的坐标系和变换。

  • 理解SVG坐标系和变换(第一部分)-viewport,viewBox,和preserveAspectRatio
  • 理解SVG坐标系和变换(第二部分)-transform属性
  • 理解SVG坐标系和变换(第三部分)-建立新视窗

为了使文中的内容和解释更形象化,我创建了一个互动演示,你可以任意改变viewBox 和 preserveAspectRatio的值。

在线案例

这个例子只是主要内容的一小部分,所以看完请回来继续阅读这篇文章

遇到的问题

  1. openid登录问题
  2. webview动态src
  3. 支付功能
  4. 分享功能
  5. 扫描普通二维码跳转特定页面
  6. 返回按钮缺失问题

HTTP Upgrade

为了更方便地部署新协议,HTTP/1.1 引入了 Upgrade 机制,它使得客户端和服务端之间可以借助已有的 HTTP 语法升级到其它协议。这个机制在 RFC7230 的「6.7 Upgrade」这一节中有详细描述。

要发起 HTTP/1.1 协议升级,客户端必须在请求头部中指定这两个字段:

Connection: Upgrade Upgrade: protocol-name[/protocol-version]

1
2
Connection: Upgrade
Upgrade: protocol-name[/protocol-version]

客户端通过 Upgrade 头部字段列出所希望升级到的协议和版本,多个协议之间用 ,(0x2C, 0x20)隔开。除了这两个字段之外,一般每种新协议还会要求客户端发送额外的新字段。

如果服务端不同意升级或者不支持 Upgrade 所列出的协议,直接忽略即可(当成 HTTP/1.1 请求,以 HTTP/1.1 响应);如果服务端统一升级,那么需要这样响应:

HTTP/1.1 101 Switching Protocols Connection: upgrade Upgrade: protocol-name[/protocol-version] [... data defined by new protocol ...]

1
2
3
4
5
HTTP/1.1 101 Switching Protocols
Connection: upgrade
Upgrade: protocol-name[/protocol-version]
 
[... data defined by new protocol ...]

可以看到,HTTP Upgrade 响应的状态码是 101,并且响应正文可以使用新协议定义的数据格式。

如果大家之前使用过 WebSocket,应该已经对 HTTP Upgrade 机制有所了解。下面是建立 WebSocket 连接的 HTTP 请求:

GET ws://example.com/ HTTP/1.1 Connection: Upgrade Upgrade: websocket Origin: Sec-WebSocket-Version: 13 Sec-WebSocket-Key: d4egt7snxxxxxx2WcaMQlA== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

1
2
3
4
5
6
7
GET ws://example.com/ HTTP/1.1
Connection: Upgrade
Upgrade: websocket
Origin: http://example.com
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: d4egt7snxxxxxx2WcaMQlA==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

这是服务端同意升级的 HTTP 响应:

HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: websocket Sec-WebSocket-Accept: gczJQPmQ4Ixxxxxx6pZO8U7UbZs=

1
2
3
4
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: gczJQPmQ4Ixxxxxx6pZO8U7UbZs=

在这之后,客户端和服务端之间就可以使用 WebSocket 协议进行双向数据通讯,跟 HTTP/1.1 没关系了。可以看到,WebSocket 连接的建立就是典型的 HTTP Upgrade 机制。

显然,这个机制也可以用做 HTTP/1.1 到 HTTP/2 的协议升级。例如:

GET / HTTP/1.1 Host: example.com Connection: Upgrade, HTTP2-Settings Upgrade: h2c HTTP2-Settings:

1
2
3
4
5
GET / HTTP/1.1
Host: example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings:

在 HTTP Upgrade 机制中,HTTP/2 的协议名称是 h2c,代表 HTTP/2 ClearText。如果服务端不支持 HTTP/2,它会忽略 Upgrade 字段,直接返回 HTTP/1.1 响应,例如:

HTTP/1.1 200 OK Content-Length: 243 Content-Type: text/html ...

1
2
3
4
5
HTTP/1.1 200 OK
Content-Length: 243
Content-Type: text/html
 
...

如果服务端支持 HTTP/2,那就可以回应 101 状态码及对应头部,并且在响应正文中可以直接使用 HTTP/2 二进制帧:

HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: h2c [ HTTP/2 connection ... ]

1
2
3
4
5
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
 
[ HTTP/2 connection ... ]

以下是通过 HTTP Upgrade 机制将 HTTP/1.1 升级到 HTTP/2 的 Wireshark 抓包(两张图可以对照来看):

图片 1

图片 2

根据 HTTP/2 协议中的描述,额外补充几点:

  • 41 号包中,客户端发起的协议升级请求中,必须通过 HTTP2-Settings 指定一个经过 Base64 编码过的 HTTP/2 SETTINGS 帧;
  • 45 号包中,服务端同意协议升级,响应正文中必须包含 HTTP/2 SETTING 帧(二进制格式,不需要 Base64 编码);
  • 62 号包中,客户端可以开始发送各种 HTTP/2 帧,但第一个帧必须是 Magic 帧(内容固定为 PRI * HTTP/2.0rnrnSMrnrn),做为协议升级的最终确认;

HTTP Upgrade 机制本身没什么问题,但很容易受网络中间环节影响。例如不能正确处理 Upgrade 头部的代理节点,很可能造成最终升级失败。之前我们统计过 WebSocket 的连通情况,发现大量明明支持 WebSocket 的浏览器却无法升级,只能使用降级方案。

SVG画布

canvas是绘制SVG内容的一块空间或区域。理论上,画布在所有维度上都是无限的。所以SVG可以是任意尺寸。然而,SVG通过有限区域展现在屏幕上,这个区域叫做viewport。SVG中超出视窗边界的区域会被裁切并且隐藏。

openid登录问题

微信webview的使用方法很简单,只要如下设置src就可以展示具体的网站了。

<!-- wxml --> <!-- 指向微信公众平台首页的web-view --> <web-view src=";

1
2
3
<!-- wxml -->
<!-- 指向微信公众平台首页的web-view -->
<web-view src="https://mp.weixin.qq.com/"></web-view>

微信环境里的很多网页都是用页面要实现网站的登录功能,只要把登录的信息,比如openid或者其他信息拼接到src里就好了。

这里有个问题,公众号的账号体系一般是以openid来判断唯一性的,小程序是可以获取openid的,但是小程序的openid和原公众号之类的openid是不一样的,需要将原先的openid账号体系升级为unionid账号体系。

以下是微信对unionid的介绍

获取用户基本信息(UnionID机制)

在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的。对于不同公众号,同一用户的openid不同)。公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间。

请注意,如果开发者有在多个公众号,或在公众号、移动应用之间统一用户帐号的需求,需要前往微信开放平台(open.weixin.qq.com)绑定公众号后,才可利用UnionID机制来满足上述需求。

UnionID机制说明:

开发者可通过OpenID来获取用户基本信息。特别需要注意的是,如果开发者拥有多个移动应用、网站应用和公众帐号,可通过获取用户基本信息中的unionid来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号,用户的unionid是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionid是相同的。

做完以上步骤,就可以调用小程序api wx.getUserInfo() 来获取用户信息了,此步骤需要进行后台信息解密过程,在此就不再赘述,结合小程序api文档操作就好。

获取到unioid之后,将unionid信息拼接到src就可以进行网页登录操作了(前提是网页可以用跳转链接的方式登录,类似公众号页面获取openid的形式)。

ALPN 扩展

HTTP/2 协议本身并没有要求它必须基于 HTTPS(TLS)部署,但是出于以下三个原因,实际使用中,HTTP/2 和 HTTPS 几乎都是捆绑在一起:

  • HTTP 数据明文传输,数据很容易被中间节点窥视或篡改,HTTPS 可以保证数据传输的保密性、完整性和不被冒充;
  • 正因为 HTTPS 传输的数据对中间节点保密,所以它具有更好的连通性。基于 HTTPS 部署的新协议具有更高的连接成功率;
  • 当前主流浏览器,都只支持基于 HTTPS 部署的 HTTP/2;

如果前面两个原因还不足以说服你,最后这个绝对有说服力,除非你的 HTTP/2 服务只打算给自己客户端用。

下面介绍在 HTTPS 中,浏览器和服务端之间怎样协商是否使用 HTTP/2。

基于 HTTPS 的协议协商非常简单,多了 TLS 之后,双方必须等到成功建立 TLS 连接之后才能发送应用数据。而要建立 TLS 连接,本来就要进行 CipherSuite 等参数的协商。引入 HTTP/2 之后,需要做的只是在原本的协商机制中把对 HTTP 协议的协商加进去。

Google 在 SPDY 协议中开发了一个名为 NPN(Next Protocol Negotiation,下一代协议协商)的 TLS 扩展。随着 SPDY 被 HTTP/2 取代,NPN 也被官方修订为 ALPN(Application Layer Protocol Negotiation,应用层协议协商)。二者的目标和实现原理基本一致,这里只介绍后者。如图:

图片 3

可以看到,客户端在建立 TLS 连接的 Client Hello 握手中,通过 ALPN 扩展列出了自己支持的各种应用层协议。其中,HTTP/2 协议名称是 h2

图片 4

如果服务端支持 HTTP/2,在 Server Hello 中指定 ALPN 的结果为 h2 就可以了;如果服务端不支持 HTTP/2,从客户端的 ALPN 列表中选一个自己支持的即可。

并不是所有 HTTP/2 客户端都支持 ALPN,理论上建立 TLS 连接后,依然可以再通过 HTTP Upgrade 进行协议升级,只是这样会额外引入一次往返。

视窗

视窗是一块SVG可见的区域。你可以把视窗当做一个窗户,透过这个窗户可以看到特定的景象,景象也许完整,也许只有一部分。

SVG的视窗类似访问当前页面的浏览器视窗。网页可以是任何尺寸;它可以大于视窗宽度,并且在大多数情况下都比视窗高度要高。然而,每个时刻只有一部分网页内容是透过视窗可见的。

整个SVG画布可见还是部分可见取决于这个canvas的尺寸以及preserveAspectRatio属性值。你现在不需要担心这些;我们之后会讨论更多的细节。

你可以在最外层<svg>元素上使用widthheight属性声明视窗尺寸。

<!-- the viewport will be 800px by 600px --> <svg width="800" height="600"> <!-- SVG content drawn onto the SVG canvas --> </svg>

1
2
3
4
<!-- the viewport will be 800px by 600px -->
<svg width="800" height="600">
    <!-- SVG content drawn onto the SVG canvas -->
</svg>

在SVG中,值可以带单位也不可以不带。一个不带单位的值可以在用户空间中通过用户单位声明。如果值通过用户单位声明,那么这个值的数值被认为和px单位的数值一样。这意味着上述例子将被渲染为800px*600px的视窗。

你也可以使用单位来声明值。SVG支持的长度单位有:emexpxptpccmmmin和百分比。

一旦你设定最外层SVG元素的宽高,浏览器会建立初始视窗坐标系和初始用户坐标系。

webview动态src

微信的webview有个坑的地方,不会动态的监听src的变化,这就造成了一个问题,要通过改变src实现页面跳转就不可以了。
我尝试了一些方法之后,找到了一个解决方案:

微信webview在页面load的时候会加载一次webview,我们就利用这个特性来实现动态src问题。

  1. 首先把要跳转的链接信息设置成全局变量,要改变src的时候,先把要src以’?‘拆分为链接和参数两部分,存入全局函数,再调用onLoad就可以实现webview刷新了。
  2. 页面跳转时,我们也需要src的动态刷新,所以要把链接信息存入全局函数;页面跳转时,onShow函数会被调用,这时候再调用一次onLoad就可以了。

data: { url: '', loaded: false } // 小程序js里的onLoad函数可以写成这样 onLoad: function () { this.setData({ url: getApp().globalData.urlToken + '?' + getApp().globalData.urlData }) }, changUrl: function () { getApp().globalData.urlToken = '' getApp().globalData.urlToken = 'a=1&b=2' // 直接调用onLoad,就会实现src的刷新 this.onLoad() }, onShow: function () { if (!this.data.loaded) { // 第一次不运行 this.setData({ loaded: true }) return } // 直接调用onLoad,就会实现src的刷新 this.onLoad() } // wxml可以写成这样 <web-view src="{{url}}"></web-view>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
data: {
    url: '',
    loaded: false
}
// 小程序js里的onLoad函数可以写成这样
onLoad: function () {
    this.setData({
      url: getApp().globalData.urlToken + '?' +  getApp().globalData.urlData
    })
},
changUrl: function () {
    getApp().globalData.urlToken = 'https://www.example.com'
    getApp().globalData.urlToken = 'a=1&b=2'
    // 直接调用onLoad,就会实现src的刷新
    this.onLoad()
},
onShow: function () {
    if (!this.data.loaded) {
      // 第一次不运行
      this.setData({
        loaded: true
      })
      return
    }
    // 直接调用onLoad,就会实现src的刷新
    this.onLoad()
  }
 
// wxml可以写成这样
<web-view src="{{url}}"></web-view>

小结

看到这里,相信你一定可以很好地回答本文开头提出的问题。

HTTP/2 需要基于 HTTPS 部署是当前主流浏览器的要求。如果你的 HTTP/2 服务要支持浏览器访问,那就必须基于 HTTPS 部署;如果只给自己客户端用,可以不部署 HTTPS(这个页面列举了很多支持 h2c 的 HTTP/2 服务端、客户端实现)。

支持 HTTP/2 的 Web Server 基本都支持 HTTP/1.1。这样,即使浏览器不支持 HTTP/2,双方也可以协商出可用的 HTTP 版本,没有兼容性问题。如下表:

浏览器 服务器 协商结果
不支持 HTTP/2 不支持 HTTP/2 不协商,使用 HTTP/1.1
不支持 HTTP/2 支持 HTTP/2 不协商,使用 HTTP/1.1
支持 HTTP/2 不支持 HTTP/2 协商,使用 HTTP/1.1
支持 HTTP/2 支持 HTTP/2 协商,使用 HTTP/2

当然,本文讨论的是通用情况。对于自己实现的客户端和服务端,如果打算使用 HTTP/2 ClearText,由于 HTTP Upgrade 协商会增加一次往返,可以要求双方必须支持 HTTP/2,直接发送 HTTP/2 数据,不走协商。

打赏支持我写出更多好文章,谢谢!

打赏作者

初始坐标系

初始视窗坐标系是一个建立在视窗上的坐标系。原点(0,0)在视窗的左上角,X轴正向指向右,Y轴正向指向下,初始坐标系中的一个单位等于视窗中的一个”像素”。这个坐标系统类似于通过CSS盒模型在HTML元素上建立的坐标系。

初始用户坐标系是建立在SVG画布上的坐标系。这个坐标系一开始和视窗坐标系完全一样-它自己的原点位于视窗左上角,x轴正向指向右,y轴正向指向下。使用viewBox属性,初始用户坐标系统-也称当前坐标系,或使用中的用户空间-可以变成与视窗坐标系不一样的坐标系。我们在一下节中讨论如何改变坐标系。

到现在为止,我们还没有声明viewBox属性值。SVG画布的用户坐标系统和视窗坐标系统完全一样。

下图中,视窗坐标系的”标尺”是灰色的,用户坐标系(viewBox)的是蓝色的。由于它们在这个时候完全相同,所以两个坐标系统重合了。图片 5

上面SVG中的鹦鹉的外框边界是200个单位(这个例子中是200个像素)宽和300个单位高。鹦鹉基于初始坐标系在画布中绘制。

新用户空间(即,新当前坐标系)也可以通过在容器元素或图形元素上使用transform属性来声明变换。我们将在这篇文章的第二部分讨论关于变换的内容,更多细节在第三部分和最后部分中讨论。

支付功能

webview里面可以通过jssdk来实现一些小程序功能,但不能直接调用小程序的支付功能,这时候我们就需要转变一下策略了:

  1. 在网页里引入微信jssdk
  2. 在网页需要发起支付的地方,调用跳转页面的接口,控制小程序跳转到小程序的支付页面(这个要在小程序里单独写的),跳转的时候,需要把订单的一些信息都拼接到链接里,订单信息由后台返回,需要通过微信支付系统的统一下单接口,具体参看支付文档。
  3. 跳转到小程序支付页面后,由小程序页面发起支付,支付完成后跳转回webview页面,通过之前设置的动态src,控制webview跳转到特定的页面。

JavaScript

// 网页引入jssdk // 网页发起支付 wx.miniProgram.navigateTo({ // payData由后台返回,主要是需要统一下单平台的prepay_id url: '../pay/index?data=' + encodeURIComponent(JSON.stringify(payData)) }) // 微信支付页面 onLoad: function (option) { let page = this try { let data = JSON.parse(option.data) if (!data || !data.prepay_id) { console.error('支付参数错误,请稍后重试', data) } wx.requestPayment({ timeStamp: '' + data.timestamp, nonceStr: data.nonceStr, package: 'prepay_id=' + data.prepay_id, paySign: data.paySign, signType: data.signType, success: function (res) { getApp().globalData.urlToken = `paySuccess.html` // 支付成功 getApp().globalData.urlData = 'data=paySuccessData' wx.navigateTo({ url: '/page/home/index', }) }, fail: function (res) { getApp().globalData.urlToken = `payError.html` // 支付失败 getApp().globalData.urlData = 'data=payErrorData' wx.navigateTo({ url: '/page/home/index', }) }, complete: function (res) { } }) } catch (e) { console.error('支付错误', e) } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 网页引入jssdk
 
// 网页发起支付
wx.miniProgram.navigateTo({
    // payData由后台返回,主要是需要统一下单平台的prepay_id
    url: '../pay/index?data=' + encodeURIComponent(JSON.stringify(payData))
})
// 微信支付页面
onLoad: function (option) {
    let page = this
    try {
      let data = JSON.parse(option.data)
      if (!data || !data.prepay_id) {
        console.error('支付参数错误,请稍后重试', data)
      }
      wx.requestPayment({
        timeStamp: '' + data.timestamp,
        nonceStr: data.nonceStr,
        package: 'prepay_id=' + data.prepay_id,
        paySign: data.paySign,
        signType: data.signType,
        success: function (res) {
          getApp().globalData.urlToken = `paySuccess.html`
          // 支付成功
          getApp().globalData.urlData = 'data=paySuccessData'
          wx.navigateTo({
            url: '/page/home/index',
          })
        },
        fail: function (res) {
          getApp().globalData.urlToken = `payError.html`
          // 支付失败
          getApp().globalData.urlData = 'data=payErrorData'
          wx.navigateTo({
            url: '/page/home/index',
          })
        },
        complete: function (res) {
        }
      })
    } catch (e) {
      console.error('支付错误', e)
    }
  }

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

图片 6 图片 7

1 赞 1 收藏 评论

viewBox

我喜欢把viewBox理解为“真实”坐标系。首先,它是用来把SVG图形绘制到画布上的坐标系。这个坐标系可以大于视窗也可以小于视窗,在视窗中可以整体可见或部分可见。

在之前的章节里,这个坐标系-用户坐标系-和视窗坐标系完全一样。因为我们没有把它声明成其他坐标系。这就是为什么所有的定位和绘制看起来是基于视窗坐标系的。因为我们一旦创建视窗坐标系(使用widthheight),浏览器默认创建一个完全相同的用户坐标系。

你可以使用viewBox属性声明自己的用户坐标系。如果你选择的用户坐标系统和视窗坐标系统宽高比(高比宽)相同,它会延伸来适应整个视窗区域(一分钟内我们就来讲个例子)。然而,如果你的用户坐标系宽高比不同,你可以用preserveAspectRatio属性来声明整个系统在视窗内是否可见,你也可以用它来声明在视窗中如何定位。我们会在下个章节里讨论这一情况的细节和例子。在这一章里,我们只讨论viewBox的宽高比符合视窗的情况-在这些例子中,preserveAspectRatio不产生影响。

在我们讨论这些例子前,我们回顾一下viewBox的语法。

分享功能

小程序直接分享的webview所在的页面,如果需要加上页面参数,那我们就需要处理一下了。

  1. webview内是不能直接发起分享的,需要先用wx.miniProgram.postMessage接口,把需要分享的信息,推送给小程序;推送给小程序的信息不是实时处理的,而是用户点击了分享按钮之后,小程序才回去读取的,这就要求每个需要分享的页面再进入的时候就发起wx.miniProgram.postMessage推送分享信息给小程序。
  2. 小程序页面通过bindmessage绑定的函数读取post信息,分享的信息会是一个列表,我们取最后一个分享就好,把分享信息处理好,存到data里面以便下一步onShareAppMessage调用。
  3. 用户点击分享时,会触发onShareAppMessage函数,在里面设置好对应的分享信息就好了。
  4. onload函数有一个option参数的,可以读取页面加载时url里带的参数,这时要对原先的onload函数进行改造,实现从option里读取链接信息。

JavaScript

// 网页wx.miniProgram.postMessage wx.miniProgram.postMessage({ data: { link: shareInfo.link, title: shareInfo.title, imgUrl: shareInfo.imgUrl, desc: shareInfo.desc } }) // 小程序index wxml设置 <web-view src="{{url}}" bindmessage="bindGetMsg"></web-view> // 小程序index js bindGetMsg: function (e) { if (!e.detail) { return } let list = e.detail.data if (!list || list.length === 0) { return } let info = list[list.length - 1] if (!info.link) { console.error('分享信息错误', list) return } let tokens = info.link.split('?') this.setData({ shareInfo: { title: info.title, imageUrl: info.imgUrl, path: `/page/index/index?urlData=${encodeURIComponent(tokens[1])}&urlToken=${tokens[0]}` } }) }, onShareAppMessage: function (res) { if (res.from === 'button') { // 来自页面内转发按钮 console.log(res.target) } let that = this return { title: that.data.shareInfo.title, path: that.data.shareInfo.path, imageUrl: that.data.shareInfo.imageUrl, success: function (res) { // 转发成功 }, fail: function (res) { // 转发失败 } } }, onLoad: function (option) { if (option.urlToken) { getApp().globalData.urlToken = option.urlToken } if (option.urlData) { getApp().globalData.urlData = option.urlData } this.setData({ url: getApp().globalData.urlToken + '?'

  • getApp().globalData.urlData }) },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 网页wx.miniProgram.postMessage
wx.miniProgram.postMessage({
  data: {
    link: shareInfo.link,
    title: shareInfo.title,
    imgUrl: shareInfo.imgUrl,
    desc: shareInfo.desc
  }
})
// 小程序index wxml设置
<web-view src="{{url}}" bindmessage="bindGetMsg"></web-view>
// 小程序index js
bindGetMsg: function (e) {
    if (!e.detail) {
      return
    }
    let list = e.detail.data
    if (!list || list.length === 0) {
      return
    }
    let info = list[list.length - 1]
    if (!info.link) {
      console.error('分享信息错误', list)
      return
    }
    let tokens = info.link.split('?')
    this.setData({
      shareInfo: {
        title: info.title,
        imageUrl: info.imgUrl,
        path: `/page/index/index?urlData=${encodeURIComponent(tokens[1])}&urlToken=${tokens[0]}`
      }
    })
},
onShareAppMessage: function (res) {
    if (res.from === 'button') {
      // 来自页面内转发按钮
      console.log(res.target)
    }
    let that = this
    return {
      title: that.data.shareInfo.title,
      path: that.data.shareInfo.path,
      imageUrl: that.data.shareInfo.imageUrl,
      success: function (res) {
        // 转发成功
      },
      fail: function (res) {
        // 转发失败
      }
    }
},
onLoad: function (option) {
    if (option.urlToken) {
      getApp().globalData.urlToken = option.urlToken
    }
    if (option.urlData) {
      getApp().globalData.urlData = option.urlData
    }
    this.setData({
      url: getApp().globalData.urlToken + '?' +  getApp().globalData.urlData
    })
},

关于作者:JerryQu

图片 8

专注 Web 开发,关注 Web 性能优化与安全。 个人主页 · 我的文章 · 2 ·   

图片 9

viewBox语法

viewBox属性接收四个参数值,包括:<min-x>, <min-y>, width 和 height

CSS

viewBox = <min-x> <min-y> <width> <height>

1
viewBox = <min-x> <min-y> <width> <height>

<min-x> 和 <min-y> 值决定viewBox的左上角,widthheight决定视窗的宽高。这里要注意视窗的宽高不一定和父<svg>元素的宽高一样。<width><height>值为负数是不合法的。值为0的话会禁止元素的渲染。

注意视窗的宽度也可以在CSS中设置为任何值。例如:设置width:100%会让SVG视窗在文档中自适应。无论viewBox的值是多少,它会映射为外层SVG元素计算出的宽度值。

设置viewBox的例子如下:

<!-- The viewBox in this example is equal to the viewport, but it can be different --> <svg width="800" height="600" viewBox="0 0 800 600"> <!-- SVG content drawn onto the SVG canvas --> </svg>

1
2
3
4
<!-- The viewBox in this example is equal to the viewport, but it can be different -->
<svg width="800" height="600" viewBox="0 0 800 600">
    <!-- SVG content drawn onto the SVG canvas -->
</svg>

如果你之前在其他地方看到过viewBox,你也许会看到一些解释说你可以用viewBox属性通过缩放或者变化使SVG图形变换。这是真的。我将深入探究并且告诉你甚至可以使用viewBox来切割SVG图形。

理解viewBox和视窗之间差异最好的方法是亲身观察。所以让我们看一些例子。我们将从viewBox和viewport的宽高比相同的例子开始,所以我们还不需要深入了解preserveAspectRatio

扫描普通二维码跳转特定页面

除了分享功能之外,小程序还可以通过配置,实现扫描普通二维码跳转特定页面的功能。

以下是微信对此功能的介绍

为了方便小程序开发者更便捷地推广小程序,兼容线下已有的二维码,微信公众平台开放扫描普通链接二维码跳转小程序能力。

功能介绍

普通链接二维码,是指开发者使用工具对网页链接进行编码后生成的二维码。

线下商户可不需更换线下二维码,在小程序后台完成配置后,即可在用户扫描普通链接二维码时打开小程序,使用小程序的功能。
对于普通链接二维码,目前支持使用微信“扫一扫”或微信内长按识别二维码跳转小程序.

二维码规则

根据二维码跳转规则,开发者需要填写需要跳转小程序的二维码规则。要求如下:

  1. 二维码规则的域名须通过ICP备案的验证。
  2. 支持http、https、ftp开头的链接(如:、)。
  3. 一个小程序帐号可配置不多于10个二维码前缀规则。

前缀占用规则

开发者可选择是否占用符合二维码匹配规则的所有子规则。如选择占用,则其他帐号不可申请使用满足该前缀匹配规则的其他子规则。

如:若开发者A配置二维码规则:,并选择“占用所有子规则“,其他开发者将不可以配置满足前缀匹配的子规则如。

我推荐的方式

webview实现方式

  1. 设置跳转功能小程序后台就可以设置,链接是分为四部分,路https://www.example.com/wxmin…。

    https://www.example.com 域名
    /wxmini/ 小程序前置规则,需要在服务器上建一个文件夹,并且把验证文件放在文件夹线
    home.html 需要跳转的网页页面
    a=1 跳转页面的参数
  2. 对onload函数再进行处理,实现普通二维码跳转。

JavaScript

// 对index onLoad在进行处理 onLoad: function (option) { this.resetOption(option) if (option.urlToken) { getApp().globalData.urlToken = option.urlToken } if (option.urlData) { getApp().globalData.urlData = option.urlData } this.setData({ url: getApp().globalData.urlToken + '?' + getApp().globalData.urlData }) }, resetOption: function (option) { if (!option) { return } if (option.q) { option.q = decodeURIComponent(option.q) if (option.q.indexOf('') == -1) { return } let tmp = option.q.replace('/wxmini', '') let tmps = tmp.split('?') option.urlToken = tmps[0] option.urlData = tmps[1] } else { option.urlData = decodeURIComponent(option.urlData) } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 对index onLoad在进行处理
onLoad: function (option) {
    this.resetOption(option)
    if (option.urlToken) {
      getApp().globalData.urlToken = option.urlToken
    }
    if (option.urlData) {
      getApp().globalData.urlData = option.urlData
    }
    this.setData({
      url: getApp().globalData.urlToken + '?' +  getApp().globalData.urlData
    })
},
resetOption: function (option) {
    if (!option) {
      return
    }
    if (option.q) {
      option.q = decodeURIComponent(option.q)
      if (option.q.indexOf('https://www.example.com/wxmini/') == -1) {
        return
      }
      let tmp = option.q.replace('/wxmini', '')
      let tmps = tmp.split('?')
      option.urlToken = tmps[0]
      option.urlData = tmps[1]
    } else {
      option.urlData = decodeURIComponent(option.urlData)
    }
}

与viewport宽高比相同的viewBox

我们从一个简单的例子开始。这个例子中的viewBox的尺寸是视窗尺寸的一半。在这个例子中我们不改变viewBox的原点,所以<min-x><min-y>都设置成0。viewBox的宽高是viewport宽高的一半。这意味着我们保持宽高比。

<svg width="800" height="600" viewBox="0 0 400 300"> <!-- SVG content drawn onto the SVG canvas --> </svg>

1
2
3
<svg width="800" height="600" viewBox="0 0 400 300">
    <!-- SVG content drawn onto the SVG canvas -->
</svg>

所以,viewBox="0 0 400 300"到底有什么用呢?

  • 它声明了一个特定的区域,canvas横跨左上角的点(0,0)到点(400,300)
  • SVG图像被这个区域裁切
  • 区域被拉伸(类似缩放效果)来充满整个视窗。
  • 用户坐标系被映射到视窗坐标系-在这种情况下-一个用户单位等于两个视窗单位。

下面的图片展示了在我们例子中把上面的viewBox应用到<svg> 画布中的效果。灰色单位代表视窗坐标系,蓝色坐标系代表viewBox建立的用户坐标系。

图片 10

任何在SVG画布中画的内容都会被对应到新的用户坐标系中。

我喜欢像Google地图一样通过viewBox把SVG画布形象化。在Google地图中你可以在特定区域缩放;这个区域是唯一可见的,并且在浏览器视窗中按比例增加。然而,你知道地图的剩余部分还在那里,但是不可见因为它超出视窗的边界-被裁切了。

现在让我们试着改变<min-x><min-y>的值。都设置为100。你可以设置成任何你想要的值。宽高比还是和视窗的宽高比一样。

<svg width="800" height="600" viewBox="100 100 200 150"> <!-- SVG content drawn onto the SVG canvas --> </svg>

1
2
3
<svg width="800" height="600" viewBox="100 100 200 150">
    <!-- SVG content drawn onto the SVG canvas -->
</svg>

添加viewBox="100 100 200 150"的效果和之前例子中一样都是裁切的效果。图形被裁切然后拉伸来充满整个视窗区域。

图片 11

再一次,用户坐标系被映射到视窗坐标系-200用户单位映射为800视窗单位因此每个用户单位等于四个视窗单位。结果像你看到的那样是放大的效果。

另外注意,在这个时候,为<min-x><min-y>声明非0的值对图形有变换的效果;更加特别的是,SVG 画布看起来向上拉伸100个单位,向左拉伸100个单位(transform="translate(-100 -100)")。

的确,作为规范说明,viewBox属性的影响在于用户代理自动添加适当的变换矩阵来把用户空间中具体的矩形映射到指定区域的边界(通常是视窗)”

这是一个很棒的说明我们之前已经提到的内容的方法:图形被裁切然后被缩放以适应视窗。这个说明随后增加了一个注释:“在一些情况下用户代理在缩放变换之外需要增加一个移动变换。例如,在最外层的svg元素上,如果viewBox属性对<min-x><min-y>声明非0值得那么就需要移动变换。”

为了更好演示移动变换,让我们试着给<min-x><min-y>添加-100。移动效果类似transform="translate(100 100)";这意味着图形会在切割和缩放后移动到右下方。回顾倒数第二个裁切尺寸为400*300的例子,添加新的无效<min-x><min-y>值,新的代码如下:

<svg width="800" height="600" viewBox="-100 -100 300 200"> <!-- SVG content drawn onto the SVG canvas --> </svg>

1
2
3
<svg width="800" height="600" viewBox="-100 -100 300 200">
    <!-- SVG content drawn onto the SVG canvas -->
</svg>

给图形添加上述viewBox transformation的结果如下图所示:图片 12

注意,与transform属性不同,因为viewBox自动添加的tranfomation不会影响有vewBox属性的元素的x,y,宽和高等属性。因此,在上述例子中展示的带有width,heightviewBox属性的svg元素,widthheight属性代表添加viewBox 变换之前的坐标系中的值。在上述例子中你可以看到初始(灰色)viewport坐标系甚至在<svg>上使用了viewBox属性后仍然没有影响。

另一方面,像tranform属性一样,它给所有其他属性和后代元素建立了一个新的坐标系。你还可以看到在上述例子中,用户坐标系是新建立的-它不是保持像初始用户坐标系和使用viewBox前的视窗坐标系一样。任何<svg>后代会在这个的用户坐标系中定位和确定尺寸,而不是初始坐标系。

最后一个viewBox的例子和前一个类似,但是它不是切割画布,我们将在viewport里扩展它并看它如何影响图形。我们将声明一个宽高比视窗大的viewBox,并依然保持viewport的宽高比。我们在下一章里讨论不同的宽高比。

在这个例子中,我们将viewBox的尺寸设为viewport的1.5倍。

<svg width="800" height="600" viewBox="0 0 1200 900"> <!-- SVG content drawn onto the SVG canvas --> </svg>

1
2
3
<svg width="800" height="600" viewBox="0 0 1200 900">
    <!-- SVG content drawn onto the SVG canvas -->
</svg>

现在用户坐标系会被放大到1200*900。它会被映射到视窗坐标系,用户坐标系中的每一个单位水平方向上等于视窗坐标系中的viewport-width / viewBox-width,竖直方向上等于viewport-height / viewBox-height。这意味着,在这种情况下,每一个用户坐标系中的x-units等于viewport坐标系中的0.66x-units,每个用户y-unit映射成0.66的viewport y-units。

当然,理解这些最好的方法是把结果视觉化。viewBox被缩放到适应下图所示的viewport。因为图形在画布里基于新的用户坐标系绘制的,而不是视窗坐标系,它看起来比视窗小。图片 13

到目前为止,我们所有的例子的宽高比都和视窗一致。但是如果viewBox中声明的宽高比和视窗中的不一样会发生什么呢?例如,试想我们把视窗的尺寸设为1000*500。宽高比不再和视窗的一样。在例子中使用viewBox="0 0 1000 500"的结果如下图:图片 14

用户坐标系。因此图形在视窗中定位:

  • 整个viewBox适应视窗。
  • 保持viewBox的宽高比。viewBox没有被拉伸来覆盖视窗区域。
  • viewBox在视窗中水平垂直居中。

这是默认表现。那用什么控制表现呢?如果我们想改变视窗中viewBox的位置呢?这就需要用到preserveAspectRatio属性了。

返回按钮缺失问题

如果web页面是在第一个页面的话,这时候会有一个问题,小程序的返回按钮就没有了,webview无法使用微信的返回按钮了,这时候只要在webview页面前多加一个跳转页面就好了(第一个页面也可以设置成获取用户权限的页面,不过我感觉这样体验不好,也不是所有页面都要用户获取了权限才可以使用)

最终的页面层级

JavaScript

"pages": [ "page/index/index", // 首页,处理onload里的option内容,为了返回按钮设置的 "page/home/index", // webview所在的页面 "page/auth/index", // 获取用户权限的页面 "page/pay/index", // 支付页面 "page/error/index" // 错误信息页面 ],

1
2
3
4
5
6
7
"pages": [
    "page/index/index", // 首页,处理onload里的option内容,为了返回按钮设置的
    "page/home/index", // webview所在的页面
    "page/auth/index", // 获取用户权限的页面
    "page/pay/index", // 支付页面
    "page/error/index" // 错误信息页面
  ],

preserveAspectRatio属性

preserveAspectRatio属性强制统一缩放比来保持图形的宽高比。

如果你用不同于视窗的宽高比定义用户坐标系,如果像我们在之前的例子中看到的那样浏览器拉伸viewBox来适应视窗,宽高比的不同会导致图形在某些方向上扭曲。所以如果上一个例子中的viewBox被拉伸以在所有方向上适应视窗,图形看起来如下:图片 15

当给viewBox设置0 0 200 300的值时扭曲显而易见(显然这很不理想),这个值小于视窗尺寸。我故意选择这个尺寸从而让viewBox匹配鹦鹉边界盒子的尺寸。如果浏览器拉伸图像来适应整个视窗,看起来会像下面这样:图片 16

preserveAspectRatio属性让你可以在保持宽高比的情况下强制统一viewBox的缩放比,并且如果不想用默认居中你可以声明viewBox在视窗中的位置。

本文由澳门新葡亰平台官网发布于web前端,转载请注明出处:网页程序迁移至微信小程序web,明白SVG坐标系和

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。