为了适应不同尺寸、不同DPR(device pixel rate)的屏幕,微信小程序通过rpx这个长度单位来实现自适应布局,理论上只要屏幕高宽比变化不大,小程序可以在任意屏幕上无损渲染。

基本概念

在微信小程序中,利用style属性、wxss样式文件,为组件设置样式,可以使用rpx、vw/vh、px几种单位设计组件的布局。要理解这几种单位的区别,需要了解物理像素和设备无关像素(DIP):

  • 物理像素:物理像素和设备相关,其大小在不同设备上可能不一样,像素密度越大,物理像素宽度越小。一般以px为单位,但是在CSS中px指的是DIP单位,浏览器内核中一般以in_pixel后缀来标识物理像素。
  • DIP:设备不相关的一个单位,是一个绝对大小值。
  • DSF:device scale factor,即一个DIP单位所占物理像素的比例。类似的概念如dpr等于:1个DIP/1个物理像素宽度比,例如iphone6的物理像素宽度是750px,dpr=2,那么一个DIP等于两个物理像素。

然后介绍一下微信小程序用到的几个单位:

rpx

参考微信官方文档介绍:

https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html#%E5%B0%BA%E5%AF%B8%E5%8D%95%E4%BD%8D

rpx不是绝对值,它是将屏幕固定等分为750份,每一份就是1rpx,所以屏幕越宽1rpx越大,微信小程序框架解析样式时,会将rpx转换成CSS的px,即:

A (rpx)* window.screen.width / 750 = B(px)

vw/vh

vw/vh和rpx类似是一个相对单位,但是以viewport的高度或宽度为比例v即viewport,浏览器内核原生支持,关键代码:

  // Viewport size that should be used for viewport units (i.e. 'vh'/'vw').
  // May include the size of browser controls. See implementation for further
  // documentation.
  FloatSize LocalFrameView::ViewportSizeForViewportUnits() const {
......
  	  zoom = GetFrame().PageZoomFactor();
......
 	layout_size.SetWidth(layout_view_item.ViewWidth(kIncludeScrollbars) / zoom);
  	layout_size.SetHeight(layout_view_item.ViewHeight(kIncludeScrollbars) / zoom);
......
    return layout_size;
  }

获取到viewport size,后续vw/vh都根据这个值按比例转换为像素;这里除以了一个zoom,它就是DSF的值,所以viewport大小也是DIP单位。

px

px指CSS中的px,参考chromium官方定义:

https://www.chromium.org/developers/design-documents/blink-coordinate-spaces/#types-of-pixels

CSS pixels: CSS defines its own pixel type that is also independent of physical pixels. When there is no Browser-Zoom, Pinch-Zoom, or CSS transforms applied, CSS pixels and DIPs are equivalent. However, zoom can make CSS pixels bigger or smaller relative to DIPs.

在不缩放的情况下,1px等于1DIP。后面会介绍到zoom发生变化时,1px(CSS)也会发生变化。

UseZoomForDSF

Linux平台上,这个开关是默认打开的(–enable-use-zoom-for-dsf),它的意思是使用跟页面缩放一样的技术来实现对 DSF 的处理,内核在Layout阶段就会进行坐标的缩放。相比于按照1x比例Paint,然后按照DSF缩放Draw,使用zoom的好处是画面可以任意缩放而不会产生失真。

参考chromium官方文档:

https://www.chromium.org/developers/design-documents/blink-coordinate-spaces/#types-of-pixels

It changes the size of a CSS pixel relative to a device independent pixel and so it will cause page layout to change. Throughout Blink, Browser-zoom is referred to as “Page Zoom” or “Zoom” more generally.

也就是说DSF被当成了zoom,而zoom会影响一个CSS 1px的大小,同时会影响布局,文字字号、图片解码大小等等,渲染出来的效果类似于矢量图形。

实验

一. 修改屏幕属性

  • 修改窗口物理像素尺寸,从原来的1920*720,调整为3840*1440
  • 将–force-device-scale-factor选项设置为2,设置一个强制的DSF。

经过上述修改,通过Display获取的屏幕物理像素大小就是3840*1440,而在打开USE_AURA宏开关的情况下,通过Display::Bounds获取的是DPI单位尺寸:3840/2 * 1440/2 = 1920*720

对比了一下只修改屏幕尺寸,和同时修改屏幕尺寸和DSF的效果对比:

只修改屏幕尺寸,rpx/vw的布局是对的,但是CSS px也是基于大尺寸(3840宽度)布局的,所以看上去特别小

同时修改屏幕大小和DSF,CSS px的布局基于宽度1920,所以看上去要大一些

后面可以看到为什么这两处修改会产生这些影响:

对rpx的影响

rpx依赖window.screen.width,从下面window.screen.width获取的调用栈来看,window.screen.width是DIP单位宽度:

screen::AdjustSurfaceSize  // wayland window设置的以物理像素为单位的大小
Display::SetScaleAndBounds 
Display::bounds  // 通过Display::Bounds获取的是DPI单位尺寸
RenderWidget::GetScreenInfo
GetChromeClient().GetScreenInfo
Screen::width   // 最终window.screen.width拿到的就是DIP单位

根据rpx转换px的公式:

A (rpx)* window.screen.width / 750 = B(px)

根据微信官方文档,rpx的换算是以物理像素宽度(iphone6 750px)为基准,而window.screen.width是DIP单位,所以换算出的px是CSS的px单位,不是物理像素。

对vw/vh的影响

viewport size的大小最终也是从Display获取,但是Display::bounds已经转换成了DIP,LocalFrameView::ViewportSizeForViewportUnits又会对尺寸除以zoom,所以传递过来的必须是物理像素单位。

 对Surface大小的影响

经过上述修改,小程序组件的布局会基于3840 * 1440的物理像素尺寸,但是由于DSF设置为2,原来的rpx/px/vw/vh单位实际上会放大一倍,这样理论上组件的布局没有发生变化,但是整体效果会放大一倍,这可以通过Chromium提交到Wayland Compositor的绘制结果截图看出。

Chromim使用OpenGL/EGL渲染,最终渲染目标是一个类型为EGLSurface的窗口类型,通过eglCreateWindowSurface和传入的EGLNativeWindowType window参数创建,在Wayland渲染框架中,EGLNativeWindowType实际类型是wl_egl_window,它可以通过wl_egl_window_resize命令修改大小,其影响EGLSurface的大小,最终影响提交到父级Compositor的Surface大小。

Resize的时机,是在绘制每一帧的CompositorFrame前,检查当前viewport大小和下一帧CompositorFrame记录的viewport大小是否相同,不同就Resize一下,修改WindowSurface的大小。

从每一帧的绘制截图来看,图片的大小是3840*1440,说明图片在不失真的情况下放大了一倍。

By can

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注