css設置元素的寬高為整數,為什麼有的瀏覽器解析出來的寬高是小數?

如圖,css 設置 img 元素的 width,height,border 都是整數,但是查看時顯示的是小數,這樣就導致多個元素 float 時,根據算好的 width 不能在同一行顯示,會折行的問題。請問是什麼原因導致的這種問題?


因為你把當前的標籤頁縮放為 90% 顯示了,所以 webkit(blink)會渲染出非整數的盒子尺寸:

可以看到盒子的 border 寬度從 1px 變成了 1.111px,直接導致盒子的尺寸增加了 0.2px,導致布局出現問題。而 content box 也有微小的尺寸變化,從 96px × 38px 變成了 95.990px × 37.986px。

這涉及到兩點有關渲染的知識,下文里的「瀏覽器」指 webkit(blink):

1. 瀏覽器在縮放頁面時,對於 border 的寬度,實際的結果是縮放後的值向下取整(且不小於 1px)。這是通過修改盒子中 border 的原始寬度來實現的,這麼實現的目的,是為了讓盒子模型的尺寸數值與實際顯示效果保持一致。

33% 的頁面縮放時,border 寬度應該顯示為 1px(0.333px 小於 1px),瀏覽器會修改 border 寬度至 3px(1px / 33%),使得 3px 的 33% 縮放為 1px;

250% 的頁面縮放時,border 寬度應該顯示為 2px(2.5px 向下取整),瀏覽器會修改 border 寬度至 0.8px(2px / 250%),使得 0.8px 的 250% 縮放為 2px;

同理:90% 的頁面縮放時,border 寬度應該顯示為 1px(0.9px 小於 1px),瀏覽器會修改 border 寬度至 1.111px(1px / 90%),使得 1.111px 的 90% 縮放為 1px;

你可以在這裡:https://jsfiddle.net/midare/znt46uk0/show/ 看到上面那個 1px border 的盒子,試試縮放頁面,再檢查這個盒子的 border 寬度。

border 寬度 0.1px 級別的變化,當然會導致沒有餘量的布局出現問題。要以屏幕最小的一像素為顯示單位,同時讓 border 顯示出來,這個特性比較好理解。

除此以外,為什麼盒子尺寸還會有 0.01px 級別的變化呢?這涉及到下一點:

2. 瀏覽器是以 1 / 64 px 為單位而非整數 1px 來計算盒子尺寸與布局的,這使百分比、計算、小數點式的尺寸值成為可能,當然也會造成精度丟失。

考慮一個 100px 寬的盒子里放了一個 66.5px 和 33.5px 寬的兩個盒子,容器會剛好被撐滿,既不會因為小數部分四捨五入導致 67px + 34px &> 100px 換行,也不會無視小數部分導致 66px + 33px 剩餘 1px。

這是因為 100px 寬的容器,內部有 6400 個 1 / 64px 的顯示單位。66.5px 寬是佔用 4256 個顯示單位,33.5px 寬是 2144 個顯示單位,4256 + 2144 = 6400,當然是可以塞進去的。雖然兩個盒子最終會被渲染成 67px 與 33px 寬,但這並不是簡單四捨五入的結果,而是圖像引擎較為底層的處理。

如果像素寬度在乘以 64 倍後依然有小數點,那麼不同瀏覽器會採用不同策略(向上或者向下取整)來判定實際的寬度,但一定會進行取整,因為這是最小的處理顯示單位。

這會導致一些奇怪的現象。比如 100px 可能裝得下一個 66.5px 和一個33.51px 的盒子:

在這裡查看在線例子:decimal box sizing - JSFiddle,不同瀏覽器策略不同。

Safari 一直到 33.515625px 都還可以塞得下(比 33.5px 多出了 1 / 64px = 0.015625px),似乎是向下取整單位數、Firefox 比 33.5px 多一點都不行,似乎是向上取整單位數。Chrome 則比較詭異,我還沒有找到規律。

總之,渲染盒子尺寸的過程可以概括為:

  1. 在樣式中設置尺寸(包括通過百分比、calc() 等計算後的)

  2. 將尺寸轉換為 1 / 64px 的最小單位並取整——在這一步會有精度丟失

  3. 渲染實際尺寸——可以用 element.getBoundingClientRect() 來看到瀏覽器實際渲染時的尺寸。

上面的代碼比較直觀地說明了這一過程。在這裡查看上圖的例子:real decimal box width

回到問題中的例子,96px 為什麼會變成 95.990px,就和瀏覽器在縮放過程中,渲染單位的精度丟失有關。考慮瀏覽器是這樣進行渲染的:

  • 96px 的盒子在 90% 的頁面縮放下,是 86.4px(96 × 90%)寬;

  • 86.4px 是 5529.6(86.4 × 64)個顯示單位。假設顯示單位向下取整,那麼這個元素實際寬 5529 個單位;

  • 5529 個單位寬的盒子,實際是 86.390625px(5529 / 64)寬,比起 86.4px 的理論值,已經有精度丟失。

  • 為了便於開發,在瀏覽器介面中顯示的,則是進行縮放前的元素的大小(這一點和上面 border 的處理是一樣的),為 95.990px(86.390625 / 90%)寬

即 Math.floor(96 * 0.9 * 64) / 64 / 0.9,這就和檢查器中顯示的尺寸比較對應了(檢查器也是通過 DOM API 來獲取元素尺寸的)

看上圖代碼一目了然。

對精度丟失的猜想(向下取整),和實際的結果雖然還有微小的區別,但是已經八九不離十。實際原因可能就涉及到瀏覽器實際渲染的細節,比如更多浮點數計算的精度丟失等等。

再回到題主的問題:

  • 非整數倍縮放頁面,會導致一些難以精確預測的尺寸變化:其中 border 寬度和元素寬度各有各的問題。

  • 用 box-sizing: border-box(IE8+)會讓盒子尺寸的設定和預測容易得多的多

參考:359691 - getBoundingClientRect and JQuery returns incorrect measure values when using certain zoom levels - chromium - Monorail


瀉藥

首先去看 CSS 的 4值概念

Assigning property values, Cascading, and Inheritance

了解:定義值、計算值、使用值、實際值分別指什麼

然後去看 CSSOM 規範內的解析值概念

https://www.w3.org/TR/cssom-1/#specified-value

最後開發工具是使用 getComputedStyle API 來做元素寬高等標註的

通過解析值概念可以知道 為了解決 getComputedStyle 的歷史遺留問題

在獲取 width、height 等值時如果 display 不是 none,則返回的是 CSS 4值中的使用值

而使用值只是布局計算的絕對結果(可以存在小數值)

實際值才是才是最終設備可用的值(如不能存在小數範圍的像素值等)

所以,開發工具才有顯示成小數,但實際值為整數情況。


沒有演示地址,暫且不知道原因!但是解決方案有一個,img外層加一個容器!設置寬高,並且設置溢出隱藏


我估摸是樣式沒有重置的問題,按照道理chrome的內核是沒啥問題的


你縮放瀏覽器了吧。應該


原因暫不知道。不過實際寬度應該是98*40(96*38 + 1px 邊框?),不用糾結小數點後的。因為顯示器根本不支持。


推薦閱讀:

開發vue(或類似的MVVM框架)的過程中,需要面對的主要問題有哪些?
研究js框架源碼用處真的大么?
畢業後在建築工地上呆了半年後,辭職後堅決打算轉行做前端,但是卻被現實動搖了——還能做什麼工作?
現在作為一名自學前端開發的學生,對網站的前端與後台數據交互不懂,想學習ajax,很迷茫,不知從何學起?
如何理解 React Fiber 架構?

TAG:前端開發 | HTML | CSS | DivCSS | CSS布局 |