平常小程序写的多一些,简单总结一下原理。但因为小程序也没开源,只能参考相关文档以及开发者工具慢慢理解了。
理解小程序原理的突破口就是开发者工具了,开发者工具是基于 NW.js
,一个基于 Chromium
和 node.js
的应用运行时。同时暴漏了 debug
的入口。
点开后就是一个新的 devTools
的窗口,这里我们可以找到预览界面的 dom
。
小程序界面是一个独立的 webview
,也就是常说的视图层,可以在命令行执行 document.getElementsByTagName('webview')
,可以看到很多 webview
。
我这边第 0
个就是 pages/index/index
的视图层,再通过 document.getElementsByTagName('webview')[0].showDevTools(true)
命令单独打开这个 webview
。
熟悉的感觉回来了,其实就是普通的 html/css
,小程序的原理的突破口也就在这里了。
这篇文章简单看一下页面的样式是怎么来的,也就是 wxss
做了什么事情。
源码中 data1
的样式:
开发中工具中对应的样式:
rpx
的单位转成了 px
,同时保留网页不认识的属性名,大概就是为了方便的看到当前类本身的属性和一些文件信息。
这个样式是定义在 <style>
中,
让我们展开 <head>
找一下:
data1
确实在 <style>
中,继续搜索,可以看到这里 <style>
中的内容是通过在 <script>
执行 eval
插入进来的。
把这一段代码丢给 chatGPT
整理一下:
来一段一段看一下:
设备信息
1 | var BASE_DEVICE_WIDTH = 750; |
主要更新了几个变量,deviceWidth
、deviceDPR
,像素相关的知识很久很久以前写过一篇文章 分辨率是什么?。
这里再补充一下,这里的 deviceWidth
是设备独立像素(逻辑像素),是操作系统为了方便开发者而提供的一种抽象。看一下开发者工具预设的设备:
如上图,以 iphone6
为例,宽度是 375
,事实上 iphone6
宽度的物理像素是 750
。
所以就有了 Dpr
的含义, iphone6
的 dpr
是 2
, 1px
相当于渲染在两个物理像素上。
rpx 转换
1 | var eps = 1e-4; |
核心就是这一行 number = number / BASE_DEVICE_WIDTH * (newDeviceWidth || deviceWidth);
,其中 BASE_DEVICE_WIDTH
是 750
,也就是微信把屏幕宽度先强行规定为了 750
,先用用户设定的 rpx
值除以 750
算出一个比例,最后乘上设备的逻辑像素。
如果设备是 iphone6
,那么这里设备的逻辑像素就是 350
,所以如果是 2rpx
,2/750*375=1
最后算出来就是 1px
,实际上在 iphone6
渲染的是两个物理像素,也就是常常遇到的 1px
过粗的问题,解决方案可以参考这篇 前端移动端1px问题及解决方案。
接下来一行 number = Math.floor(number + eps);
是为了解决浮点数精度问题,比如除下来等于 3.9999999998
,实际上应该等于 4
,只是浮点数的问题导致没有算出来 4
,加个 eps
,然后向下 floor
去整,就可以正常得到 4
了,关于浮点数可以看 一直迷糊的浮点数。
接着往下看:
1 | if (number === 0) { |
在 transformRPX
函数整个代码里第一行 if (number === 0) return 0;
,number
等于 0
已经提前结束了,所以这里 number
得到 0
就是因为除的时候得到了一个小数。
如果 deviceDPR === 1
,说明逻辑像素和物理像素是一比一的,不可能展示半个像素,直接 return 1
。
如果不是 iOS
也直接返回 1
,这是因为安卓手机厂商众多,即使 deviceDPR
大于 1
,也不一定支持像素传小数,传小数可能导致变 0
或者变 1
,为了最大可能的保证兼容性,就直接返回 1
。
对于苹果手机,据说是从 iOS 8
开始支持 0.5px
的,但没找到当时的官方说明:
因此上边的代码中,对于 deviceDPR
大于 1
,并且是苹果手机的就直接返回 0.5
了。
生成 css
1 | setCssToHead( |
通过调用 setCssToHead
把上边传的数组拼接为最终的 css
。
核心逻辑就是循环上边的数组,如果数组元素是字符串直接相加就好,如果是数组 [1]
、[0, 50]
这样,需要特殊处理下:
核心逻辑是 makeup
函数:
1 | function makeup(file, opt) { |
如果遇到 content
是 [1]
,也就是 op
等于 1
,添加一个前缀 res = opt.suffix + res;
。
如果遇到 content
是 [0, 50]
,也就是 op
等于 0
,这里的 50
其实就是用户写的 50rpx
的 50
,因此需要调用 transformRPX
将 50
转为 px
再相加 res = transformRPX(content[1], opt.deviceWidth) + 'px' + res;
。
通过 makeup
函数,生成 css
字符串后,剩下的工作就是生成一个 style
标签插入到 head
中了。
1 | ... |
注入的全部代码
这里贴一下注入的全部代码:
1 | var BASE_DEVICE_WIDTH = 750; |
编译
剩下一个问题,我们写的代码是:
1 | .container { |
但上边分析的 <script>
生成 css
的数组是哪里来的:
1 | [ |
是微信帮我们把 wxss
进行了编译,编译工具可以在微信开发者工具目录搜索 wcsc
,Library
是个隐藏目录。
我们把这个 wcsc
文件拷贝到 index.wxss
的所在目录,然后将我们的 wxss
手动编译一下:
1 | ./wcsc -js ./index.wxss >> wxss.js |
此时会发现生成的 wxss.js
就是我们上边分析的全部代码了:
总
因此对于代码 wxss
到显示到页面中就是三步了,第一步是编译为 js
,第二步将 js
通过 eval
注入到页面,第三步就是 js
执行过程中把 rpx
转为 px
,并且把 css
注入到 style
标签中。