本文首發于 vivo網際網路技術 微信公衆号
連結:
https://mp.weixin.qq.com/s/CwLAV2j7Uxam01m1p7cXxg 作者:悟空中台研發團隊
【悟空活動中台】系列往期精彩文章:
- 《 揭秘 vivo 如何打造千萬級 DAU 活動中台 - 啟航篇 》 主要為大家講述 vivo 活動中台的能力與創新。
- 悟空活動中台 - 微元件狀态管理(上) 》介紹了活動頁内 RSC 元件之間的狀态管理和背後的設計思路。
- 悟空活動中台 - 微元件狀态管理(下) 》探索平台和跨沙箱環境下的微元件狀态管理。
一、寫在前面
作為前端工程師,頁面布局是基本功。面對悟空中台的海量的活動需求,僅僅有幾招正常的布局套路顯然是難以招架的,悟空開發者團隊從個性化需求中提煉特定場景下的共性特點,設計了多個“創意布局”方案。
本文以“滿屏”場景下的頁面布局思考為切入點,以微元件為元素單元,提供了一種新的布局方案設計思路——基于行為預設的動态布局方案,并詳細的分享了設計目的及具體實作方案,對技術基礎要求不高,是一篇男女老少皆宜的“技術甜點”。
二、靈感緣起
靈感往往并不是憑空産生的,而是與問題的出現形成因果關系,解決方案也鮮有一蹴而就,大多有一個不斷完善的演進過程,我們都執着于發現問題,分析問題,解決問題的輪回。
1、問題是什麼
1.1、用戶端茫茫多,而設計稿隻有一個
這是移動端H5頁面進行布局時面臨的核心問題之一。
随着移動端生态的日益繁榮,裝置螢幕寬高比從3:4、9:16 到 9:19、9:21,分辨率從480p、720p到1080p甚至2k,顯然設計師同學不可能針對每種場景都進行對應的創作,是以一般隻就約定的标準尺寸(如常見的1080 * 1920 )輸出一張設計稿。
而前端開發同學在實施樣式布局時,就需要能根據設計師的一張設計稿,作出适配各種不同螢幕尺寸、分辨率的效果。
1.2、避免重複勞作
如果你面對的是2個裝置,可能你隻需要寫兩套樣式去适配;
如果你面對的是20個裝置,可能累一點也能搞得定;
如果你面對的是200個、2000個裝置呢?僅僅是體力勞動已經完全不能解決問題,我們需要有更高效的辦法——制定一套規則,遵循該規則的頁面能夠在運作時自己去适配所在裝置。
2、普适性方案
為了實作頁面運作時的樣式自适應,我們從一開始的靜态布局、流式布局、到響應式布局、彈性布局,目前大家普遍采取的方案是使用rem機關的彈性布局,即基于裝置像素比(Device Pixel Ratio,簡稱DPR)計算并設定不同裝置中的根字型大小,元素尺寸采取 rem 機關的方案,具體實作此處就不再贅述。
2.1、優勢
面對不同大小、比例和素質的螢幕,隻需要寫一套樣式,就能夠做到對設計稿視覺效果的精準還原;跨屏适配的邏輯代碼可以完全直接複用,配合現有的px轉rem插件,幾乎沒有額外工作量開銷。
2.2、不足
以上方案雖然有着諸多優勢,但是有時面對特定場景也會存在适配效果不夠理想的問題。
2.2.1、滿屏場景
在單頁或者滑屏H5場景下,對任何裝置,頁面内容“恰好”占滿視口。打個比方:頁面内容就像是一個“萬能螺絲釘”,不管任何規格的螺帽(視口),它都能做到“嚴絲合縫”的填充。

上圖展示了不同規格視口中,頁面内容總能恰好占滿視口,沒有溢出也沒有留白;前文所述的普适性方案在滿屏場景下就存在一些問題。
2.2.2、元素溢出和頁面留白
由于基于DPR和rem的方案特點是動态适配且對設計稿的精确還原,是以當遇到實際可視區域與設計稿比例不一緻的情況,就會出現縱向适配問題:
- 視口比設計稿“長”時,頁面縱向無法填充一屏,出現底部留白;
- 視口比設計稿“短”時,就會出現頁面縱向内容無法一屏顯示的問題,即元素溢出。
2.3、初步優化方案
為了解決縱向适配問題,我們将頁面内容分為背景圖和内部元素兩部分,并針對性的進行屬性調整,初步可以解決問題。
2.3.1、背景适配
對于背景元素,一般有兩種方案:
- 拉伸填充
令背景直接在橫向、縱向進行平鋪;缺點是會令背景圖檔由于拉伸/收縮而産生形變,比例失衡。
background-size: 100% 100%;
- 裁切溢出
在保持背景圖比例不變的前提下,使其大小能夠完全cover視窗大小,并将多餘的橫向/縱向部分裁掉。
background-size: cover;
background-position: center;
2.3.2、内部元素定位方式
對于頁面元素,我們采用固定定位(fixed),令其相對于視窗的各個邊位置固定。
下圖展示了分别相對于視口頂部左邊、頂部右邊、底部左邊和底部右邊固定定位的元素:
2.4、“精進”的優化
2.4.1、初步優化方案的問題
這種布局方案可以做到無論是橫向還是縱向,頁面内容所占空間始終與視口區域相同,初步滿足了“滿屏”的需求,但是仍然存在不足:
- 不夠靈活
固定定位的問題在于元素始終是以自己的某條邊相對于視口的對應邊框進行定位(如:隻能是元素頂部相對于視窗頂部位置固定,而不能實作元素底部相對于視窗頂部位置固定的需求)
- 空間競争
由于所有元素根據螢幕實際寬度進行**等比縮放**,故對螢幕“剩餘空間”的利用是靜态的,即當螢幕寬高比變化時,所有元素總是 同時 “占據”或者“讓出”特定比例的空間,尤其是在空間緊湊的情況下,可能存在非重點内容元素(點綴作用)與重點内容元素“空間競争”的問題。
下圖反映了固定定位在可視區域變小的情景下,元素對“剩餘”空間的擠占:
2.4.2、預設方案進行靈活适配
為了進一步提升滿屏場景布局的效果,悟空中台團隊基于DPR & rem布局方案,借鑒了元素相對視窗固定定位的思想,提出并實作了基于行為預設的動态布局方案。
三、預設規則
即通過在使用者配置頁面的時候提供頁面背景圖和内部元素的屬性、定位行為預設,實作産出頁面對不同視口的良好适配。
1、背景圖尺寸預設
1.1、多種方案靈活可選
提供多種背景圖填充方式,供使用者靈活選擇:
- 預設——不對background-size進行設定
- 拉伸填充——橫縱平鋪
- 包含——contain
- 覆寫——cover
- 重複平鋪——repeat
2、内部元素行為預設
提供配置選項,可以對元素的縮放行為作出靈活的配置以滿足實際需求。
2.1、縮放行為預設
縮放行為預設主要解決不同視口下頁面元素間的空間競争問題。
2.1.1、元素分類
将元素分類為 主要元素 和 次要元素:
- 主要元素
頁面中需要突出的重點内容,在視口尺寸發生變化引起的空間競争中,處于優勢地位;
- 次要元素
頁面中相對不重點的内容,在視口尺寸發生變化引起的空間競争中,處于劣勢地位;
2.1.2、基準視口與實際視口
基準視口即與設計稿比例相同的視口,即如果設計稿比例是 9:16 ,則基準視口就是比例為 9:16 的視口;其他比例的視口我們稱之為 非基準視口。
實際視口即頁面運作時的視口,根據不同比例,可能是基準視口,也可能是非基準視口。
2.1.3、實際視口中的元素縮放行為
當實際視口短于基準視口,主要元素大小與基準視口保持不變,次要元素按視口比例縮小;
當實際視口長于基準視口,主要元素按視口比例放大,次要元素大小與基準視口保持不變。
經過以上縮放行為預設,可以靈活定義不同元素在實際視口中的縮放行為,解決元素因視口變化出現的空間競争問題。
2.1.4、元素類型别名
為了使營運同學更容易了解主要元素和次要元素的預期行為,我們稱放大元素為主要元素的别名,縮小元素為次要元素的别名,其餘稱為預設元素。
2.2、定位方式預設
定位方式預設為了提升元素定位的靈活性,使得元素内部的定位基準可以根據實際需要任意選取。
2.2.1、錨點
元素内部選取一個定位中心,作為錨點,将來元素的定位都是基于錨點進行計算。
錨點的設定可以讓元素的定位更加靈活:如果将元素的錨點設定為其底邊的中點,那麼令錨點吸附視口頂部即可實作元素底部相對視口頂部距離固定,這是正常固定定位無法實作的。
2.2.2、吸附性
對于一個元素,可以預設其錨點吸附于視口的頂部/底部,左邊/右邊,具體規則如下:
- 元素在水準方向或垂直方向上,不能同時吸附對應的兩條邊;比如不能令一個元素同時吸附視口頂部和視口底部;但是可以另其同時吸附視口頂部和視口左邊。
- 元素若預設吸附了視口某一條邊,則在任意規格的視口中,元素錨點相對于該條邊的距離相同(以rem為機關)。
- 若元素在水準或垂直方向上,并不吸附于任意一條邊,則令其相對于該方向上的兩條邊的距離比例固定;比如若元素同時不吸附于視口左邊和右邊,則元素相對于視口左邊和右邊的距離之比固定,值為在頁面設計器中,配置頁面時該元素距離視口左邊和右邊的距離之比。
四、預設規則的實作
本部分介紹了預設規則的具體實作,重點在于展現設計思路,示例代碼均為僞代碼。
1、基準視口與實際視口
1.1、基準視口寬高
描述基準視口的寬度與高度,我們設基準寬度用baseW表示,其值為10.8 rem(對應設計稿1080px),同理基準高度baseH的值設定為21.6 rem。
1.2、實際視口寬高
描述實際視口寬度與高度,我們設實際寬度和高度分别為realW和realH,且由于使用基于DPR和rem的方案,容易得出realW = baseW = 10.8rem;
這樣一來,實際視口與基準視口的差别就在于realH與baseH的不同。
1.3、實際視口高度計算
根據realW / realH = window.innerWidth / window.innerHeight,将realW = 10.8 rem代入即可求得實際視口的 CSS 高度:
realW = 10.8
realH = (realW * window.innerHeight) / window.innerWidht
1.4、視口高度比
實際視口與基準視口的比例,設其為windowHeightRatio,則
// 計算視口高度比
windowHeightRatio = realH / baseH
2、元素縮放行為預設的實作
2.1、縮放類型
使用scaleType描述元素縮放類型,其可選值有三個——zoomIn(放大)、zoomOut(縮小)和standard(不進行縮放)。
2.2、縮放比 scale
使用scale描述元素在實際視口與标準視口下的縮放比,設元素在基準視口下的寬高為width和height,則元素在實際視口下的寬高分别為baseW scale和baseH scale。
2.3、縮放行為目标
對于 scaleType 為zoomIn的元素,當實際視口高于基準視口時,元素縮放比為視口高度比,元素表現為放大;當實際視口不高于 基準視口時,元素縮放比為 1,元素大小保持不變。即
- 當windowHeightRatio > 1(實際視口大于基準視口)時,元素sacle = windowHeightRatio
- 當windowHeightRatio <= 1(實際視口小于基準視口)時,元素sacle = 1
對于 scaleType 為zoomOut的元素,當實際視口低于基準視口時,元素縮放比為視口高度比(realH / baseH),元素表現為縮小;當實際視口不低于基準視口時,元素縮放比為 1,元素大小保持不變。即
- 當windowHeightRatio > 1(實際視口大于基準視口)時,元素sacle = 1
- 當windowHeightRatio < 1(實際視口大于基準視口)時,元素sacle = windowHeightRatio
對于scaleType為standard的元素,表現行為是始終與設計稿尺寸保持一緻,故
- 對于任何windowHeightRatio,始終有 sacle = 1
// 根據元素縮放類型确定元素的實際縮放比
switch (scaleType) {
case 'zoomIn':
scale = windowHeightRatio >= 1 ? windowHeightRatio : 1
break
case 'zoomOut':
scale = windowHeightRatio >= 1 ? 1 : windowHeightRatio
break
default:
scale = 1
}
至此,我們已經完成了對元素縮放類型的定義及縮放比的計算,接下來我們要定義并實作的另一個預設特性——定位特性。
3、元素定位方式預設的實作
3.1、錨點
錨點的設定并不固定,可以靈活根據實際需求的效果進行設定;為便于了解,本例将其設定為元素實際寬高的中心點。
3.2、吸附性
不同視口内,頁面元素的錨點相對于視口的某一個邊的位置是定值,稱該元素吸附于該條邊,視吸附的邊的不同,可以分為吸頂、吸底、靠左和靠右;
對于某個元素,若其在水準或豎直方向并不吸附于某一條邊,而是相對于頂部到底部或左邊到右邊的距離是固定比例,則稱其為按比例居中。
3.3、元素定位
我們以視口左上角作為定位坐标系的原點( 0, 0 ),将元素的吸附性使用元素錨點相對于定位原點的距離進行描述。
令元素與基準視口頂部及左邊的距離為baseTop和baseLeft;
元素與實際視口頂部及左邊的距離為realTop和realLeft。
3.3.1 特元素與可視區域頂部距離 realTop 的計算
(1)吸頂元素
吸頂元素的特性是元素錨點與視口頂部距離固定,即
- 不同視口中,元素的高度的一半與元素頂部到到螢幕頂部的距離的和是不變的。
根據特性有如下換算關系:
height / 2 + baseTop = height * scale / 2 + realTop
由realH = baseH * scale得到
realTop = height / 2 + baseTop - (height * scale) / 2
(2) 吸底元素
特性是元素錨點與視口底部的距離固定,即
- 不同視口中,元素的高度的一半與元素底部到到螢幕底部的距離的和是不變的。
故應有如下換算關系:
baseH - ( baseTop + height / 2 ) = realH - ( realTop + height * scale / 2 )
求得
realTop = realH - baseH + (baseTop + height / 2) - (height * scale) / 2
(3)按比例居中元素
特性是元素錨點距視口頂部和底部的距離成固定比例,即
- 不同視口中,元素高度的一半加上元素頂部到螢幕頂部的距離的和的值,與元素高度的一半加上元素底部到螢幕底部的距離的和的值,這兩個值相等。
( height / 2 + baseTops ) / baseH = ( height * scale / 2 + realTop ) / realH
realTop = (realH / baseH) * (height / 2 + baseTops) - (height * scale) / 2
3.3.2、元素與可視區域左邊框距離 realLeft 的計算
(1)靠左元素
對于靠左元素,特點是錨點距離視口左邊框的距離固定,即
- 不同視口中,元素元素寬度的一半與元素左邊到螢幕左邊的距離和是固定的。
baseLeft + width / 2 = realLeft + width * scale / 2
realLeft = baseLeft + width / 2 - (width * scale) / 2
(2)靠右元素
計算過程同上文,可以得到
realLeft = realW - baseW + ( baseLeft + width / 2 ) - width * scale / 2
根據元素錨點到螢幕左右邊框距離相等,可以得到
realLeft = (realW / baseW) * (baseLeft + width / 2) - (width * scale) / 2
由于我們基于rem和DPR的布局方案的一個“準則”是視口寬度總是10.8rem,即realW實際和baseW在數值上相等,是以上述結果可以簡化為:、
realLeft = ( baseLeft + width / 2 ) - width * scale / 2
至此,我們已經完成了對元素預設規則——元素縮放特性和元素定位特性的實作,接下來需要使用這兩種特性對元素的綜合樣式進行描述。
4、元素最終樣式
4.1、定位方案的選擇
4.1.1、簡單場景
對于單一的“滿屏”需求,如一個單獨的滿屏頁面,我們隻需要對其中的元素使用固定定位(fixed)方案結合前面幾個步驟求得的scale,realTop和realLeft求得樣式即可,舉個栗子:
style = `
top: ${realTop}rem;
left: ${realLeft}rem;
width: ${width}rem;
height: ${height}rem;
transform: scale(${scale});
`
4.1.2、較為複雜的場景
如果我們的頁面需要由一連串的“滿屏”頁面組成,并且可以進行“滿屏”頁面的切換,實作類似幻燈片一樣的效果,則實際上每個“滿屏”的頁面其實是我們最終頁面的一個具備“滿屏”特性的“容器”,容器内部的元素在進行布局時,需要相對于容器進行絕對定位(absolute)。
4.1.3 使用錨點進行樣式表達
而且既然我們已經有了元素錨點的概念,使用元素錨點的偏移量進行定位是更合乎情理的,錨點即是CSS中的transform-origin屬性,即transform-origin : center,假設元素均處于預設起始位置 (top = left = 0),我們使用transform屬性對元素的偏移位置進行設定:
錨點豎直方向原位置:baseAnchorY = height / 2
錨點豎直方向目标位置:realAnchorY = realTop + height * scale / 2
根據以上可以求得:
錨點豎直方向的偏移量:offsetVertical = realAnchorY - baseAnchorY = realTop + height * scale / 2 - height / 2
同理求得錨點水準方向的偏移量:offsetHorizontal = realAnchorX - baseAnchorX
最終獲得元素樣式為:
style = `
top: 0px;
left: 0px;
width: ${width}rem;
height: ${height}rem;
transform-origin: center; // 錨點設定
transform: translateX(${offsetVertical}rem) translateY(${offsetHorizontal}rem) scale(${style.scale});
`
五、預設方案在生産中的應用
1、內建形式
目前基于行為預設的動态布局方案已經作為悟空活動中台上單頁滿屏場景的預設布局配置方案,使用者可以通過簡單的兩步操作,便可調選中元素的吸附和縮放特性進行預設:
2、産出執行個體
悟空平台已經産出許多應用了的線上專題,比如經典的
vivo 浏覽器年終策劃 | 2018 大事鑒:
六、寫在最後
基于行為預設的動态布局方案一定程度上實作了根據視口尺寸對元素定位和大小的動态設定,達到了“恰到好處的突出重點”的效果。根據業務現實情況,預設方案也可以有多種不同的靈活實作,比如元素的響應式縮放、吸附特征以及錨點位置的設定可以根據需求動态調整。
如果本文能夠對你的布局設計帶來一點點微小靈感的話,那真是深感榮幸。感謝閱讀。