In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article shows you how to look at the browser layout layout from the Chrome source code, the content is concise and easy to understand, it will definitely brighten your eyes. I hope you can get something through the detailed introduction of this article.
Suppose you have the following html/css:
This will display a box above the browser:
In order to draw this box, first of all, you need to know where to start and how big it is, and then the color of the edge stroke, so you can draw it:
Void draw (SkCanvas* canvas) {SkPaint paint; paint.setStrokeWidth (1); / / start from position (200,200), width 400W, height 100SkRect rect = SkRect::MakeXYWH (200200400100); canvas- > drawRect (rect, paint);}
The above is the code drawn with Skia, Skia is a cross-platform open source 2D graphics library, is the underlying Paint engine used by Chrome/firefox/android.
In order to get a specific value, you have to layout. What is layout? The process of converting css into information that can be directly described, such as dimension location, is called layout. The following Chrome source code explains layout:
/ / The purpose of the layout tree is to do layout (aka reflow) and store its// results for painting and hit-testing. Layout is the process of sizing and// positioning Nodes on the page.
The article "how browsers calculate CSS from Chrome source code" describes how to convert css into ComputedStyle, the above div, and the converted style is as follows:
The size of width is 50, the type is percentage, and the value of margin is 0, and the type is auto, neither of which can be used to draw directly. So we need to calculate the specific number through layout.
1. Build a layout tree
The article "looking at how browsers build DOM trees from Chrome source code" describes the process of how to html text. When the received html fragment is parsed, the construction of the Layout Tree is triggered:
Void Document::finishedParsing () {updateStyleAndLayoutTree ();}
Each non-display:none/content Node node creates a LayoutObject accordingly, with the following comments on the blink source code:
/ / Also some Node don't have an associated LayoutObjects e.g. If display: none// or display: contents is set.
And establish their father-son brotherly relationship:
LayoutObject* newLayoutObject = parentLayoutObject- Node-> createLayoutObject (style); parentLayoutObject- > addChild (newLayoutObject, nextLayoutObject)
Form a separate layout tree.
When the layout tree is established, the value of the layout is calculated using style.
two。 Calculate the layout value
Take the div above as an example, it needs to calculate its width and margin.
(1) calculate the width
The width is calculated based on the type of numerical value:
Switch (length.type ()) {case Fixed:return LayoutUnit (length.value ()); case Percent:// Don't remove the extra cast to float. It is needed for rounding on// 32-bit Intel machines that use the FPU stack.return LayoutUnit (static_cast (maximumValue * length.percent () / 100.0f);}
As shown above, in the case of Fixed, a LayoutUnit encapsulated data is returned directly, 1px = 1 styleRef (); if (marginStartLength.isAuto () & & marginEndLength.isAuto ()) {LayoutUnit centeredMarginBoxStart = std::max (LayoutUnit (), (availableWidth-childWidth) / 2); marginStart = centeredMarginBoxStart; marginEnd = availableWidth-childWidth-marginStart; return;}
Line 8 above subtracts its own width from the width of the container, divides it by 2 to get margin-left, and then subtracts its own width and margin-left from the width of the container to get margin-right. Why does margin-right have to calculate again, because the above code is a deleted version, it has another situation to deal with, here is not very important, I have omitted.
Once margin and width are calculated, they put it into the box model data structure of the layoutObject node:
M_frameRect.setWidth (width); m_marginBoxOutsets.setStart (marginLeft); (3) data structure of box model
In the source code comments of blink, the box model diagram is drawn vividly:
/ / * THE BOX MODEL * / / The CSS box model is based on a series of nested boxes:// http://www.w3.org/TR/CSS21/box.html//// |-- | / / | | | / / | margin-top | / | | / | |-- | -| | / / | / | | border-top | | / / | / / | | | |-| |-- | / | / | padding-top | # | / | | | # | / | # | / | / / | ML | BL | PL | content box | PR | SW | BR | MR | / / | | / / |-| | / | / | padding-bottom | | / / |-| -| |-- | / | # | | / | | scrollbar height # | SC | / / | | # | | | / |-- | / | / | | border-bottom | | / / | | | / | |-- | | / / | | / / | Margin-bottom | / / | | / / |-- | / BL = border-left// BR = border-right// ML = margin-left// MR = margin-right// PL = padding-left// PR = padding-right// SC = scroll corner (contains UI for resizing (see the 'resize' property) / / SW = scrollbar width
The box model above is familiar, but unlike it, it also draws the scroll bar.
The box model border and its areas are represented by a LayoutRect m_frameRect object:
/ / The CSS border box rect for this box.//// The rectangle is in this box's physical coordinates.// The location is the distance from this// object's border edge to the container's border edge (which is not// always the parent). Thus it includes any logical top/left along// with this box's margins.LayoutRect m_frameRect
The above source code comments are very clear, which means that the position of the LayoutRect is the distance from its own edge to the edge of the container, so its distance / position includes the margin value and the displacement deviation of left/top. LayoutRect records the location and size of a box:
LayoutPoint masking location; LayoutSize m_size
After calculating the width above (1) and (2), set the size and save it.
You can see some ways to get the width with this object pair in the source code, such as clientWidth:
/ / More IE extensions. ClientWidth and clientHeight represent the interior of// an object excluding border and scrollbar.LayoutUnit LayoutBox::clientWidth () const {return m_frameRect.width ()-borderLeft ()-borderRight ()-verticalScrollbarWidth ();}
ClientWidth is the width that removes border and scrollbar.
And offsetWidth is the width of frameRect-- including border and scrollbar:
/ / IE extensions. Used to calculate offsetWidth/Height.LayoutUnit offsetWidth () const override {return m_frameRect.width ();} LayoutUnit offsetHeight () const override {return m_frameRect.height ();}
The Margin area is represented by a LayoutRectOutsets, which records the upper and lower left and right values of the margin:
LayoutUnit massively topped LayoutUnit massively rightlyLayoutUnit massively bottomled LayoutUnit m_left
The calculation of width and height has been analyzed above, and the calculation of position is still different.
(4) position calculation
The position calculation is to calculate the values of x and y, or left and top, which are calculated in the following two functions:
/ / Now determine the correct ypos based off examination of collapsing margin// values.LayoutUnit logicalTopBeforeClear = collapseMargins (child, layoutInfo, childIsSelfCollapsing, childDiscardMarginBefore, childDiscardMarginAfter); / / Now place the child in the correct left positiondetermineLogicalLeftPositionForChild (child)
Use the following html as an example:
Hello, world
I'll print out the calculated results first, as follows:
[LayoutBlockFlow.cpp] location is: "190.25", "0" size is "400.5", "110th" (div-1)
[LayoutBlockFlow.cpp (925)] location is: "115,115" size is "451", "18" (div-3)
[LayoutBlockFlow.cpp (925)] location is: "50", "160" size is "681", "248" (div-2)
[LayoutBlockFlow.cpp (925)] location is: "8", "8" size is "781", "408" (body)
[LayoutBlockFlow.cpp] location is: "0", "0" size is "797", "466" (html)
Because it is a recursive process, the order printed above is from child elements to parent elements. Take div-2 as an example, its x = 50, y = 160. because the space occupied by div-1 is h = border * 2 + height = 5 * 2 + 100 = 110and div-2 has a margin-top = 50, so div-2 's y = 11050 = 160.
For div-3, because div-2 has a padding of 80px and a border of 20px, and it has a margin of 15px itself, div-3 has y = 50 + 20 + 15 = 115,115.
If you print out the inline elements as well, the result looks like this:
[LayoutBlockFlowLine.cpp (1997)] inline location is: "0", "0" size is "400.5", "10" (div-1 content)
[LayoutBlockFlow.cpp] location is: "190.25", "0" size is "400.5", "110"
[LayoutBlockFlowLine.cpp (1997)] inline location is: "0", "115" size is "451", "18" (div-3 text)
[LayoutBlockFlow.cpp] location is: "115", "115" size is "451", "18"
... (same as after)
The third line is the layoutObject created by the text node of div-3, whose line height is 18px, so its size height is 18px.
Here you can see that blank nodes between block-level elements do not generate layoutObject, which can be proved in the code:
Bool Text::textLayoutObjectIsNeeded (const ComputedStyle& style,const LayoutObject& parent) const {if (! length ()) return false; if (style.display () = = EDisplay::None) return false; if (! containsOnlyWhitespace ()) return true; / / other judgment}
In line 7 above, if the Text node contains non-white space characters, return true immediately, otherwise continue to judge:
If (parent.isLayoutBlock () & &! parent.childrenInline () & & (! prev | |! prev- > isInline ()) return false
Second line-returns false if there is a previous adjacent node and this node is not an inline element, and no layout object is created.
So the blank text node after the block-level element will not participate in the rendering, which explains why the line wrap after the block-level element will not be converted to a space. You can also see in the source code that the opening white space characters in the block-level elements will be ignored:
/ / Whitespace at the start of a block just goes away. Don't even// make a layout object for this text.
The question here is, why does it calculate recursively, that is, the operator element and then the parent element? Because some attributes must know the child element in order to know the parent element, for example, the height of the parent element is supported by the child element, but some attributes need to know the talent operator element of the parent element first, for example, the width of the child element is 50% of that of the parent element. Therefore, the layout of the current element is calculated before the child element is calculated, and then passed to the child element. After the child element is calculated, it will return whether the parent element needs to be re-layout, as follows:
/ / Use the estimated block position and lay out the child if needed. After / / child layout, when we have enough information to perform proper margin / / collapsing, float clearing and pagination, we may have to reposition and / / layout again if the estimate was wrong. Bool childNeededLayout = positionAndLayoutOnceIfNeeded (child, logicalTopEstimate, layoutInfo)
The specific calculation process, here to give one or two examples, for example, when calculating the padding-left value, it will first take the parent element's border-left and padding-left as the starting position, and then add its own margin-left to get its x / left value.
Void LayoutBlockFlow::determineLogicalLeftPositionForChild (LayoutBox& child) {LayoutUnit startPosition = borderStart () + paddingStart (); LayoutUnit initialStartPosition = startPosition; LayoutUnit childMarginStart = marginStartForChild (child); LayoutUnit newPosition = startPosition + childMarginStart; / / other code}
We know that the floating rules are more complex, so the corresponding calculation is also more complex, let's take a brief study.
(5) floating
Use the following three-column layout as an illustration:
Hello, world
Let's first look at the calculation of the width. For the div of * float: left, it will first determine whether the width requires fit content:
Bool LayoutBox::sizesLogicalWidthToFitContent (const Length& logicalWidth) const {if (isFloating () | | isInlineBlockOrInlineTable ()) return true; / / other code}
If it is floating or inlne-block, you need the width to fit the content. Because the child element is an inline text, it needs to calculate the width of the inline element, and the rules of calculation are very complicated. I'll post some of the comments here:
/ / (3) A text object Text runs can have breakable characters at the// start, the middle or the end. They may also lose whitespace off the// front if we're already ignoring whitespace. In order to compute// accurate min-width information, we need three pieces of// information.// (a) the min-width of the first non-breakable run. Should be 0 if// the text string starts with whitespace.// (b) the min-width of the last non-breakable run. Should be 0 if the// text string ends with whitespace.// (c) the min/max width of the string (trimmed for whitespace).
The second floating div, whose child element is a p tag, has already specified the width. It removes the width of the operator element plus the width of the margin value, determines whether it is floating, loops through all child elements, and takes a * value.
If you look at the calculation of the location, you can still see a little bit about the code for calculating the location, such as the calculation of float: left:
/ / if the remaining space on the current line is less than the width of float, the loop condition holds while (logicalRightOffsetForPositioningFloat (logicalTopOffset, logicalRightOffset, & heightRemainingRight)-floatLogicalLeft
< floatLogicalWidth) { //往下挪 logicalTopOffset += std::min(heightRemainingLeft, heightRemainingRight); //计算新的float left位置 floatLogicalLeft = logicalLeftOffsetForPositioningFloat( logicalTopOffset, logicalLeftOffset, &heightRemainingLeft); }}//循环结束,找到位置floatLogicalLeft = std::max( logicalLeftOffset - borderAndPaddingLogicalLeft(), floatLogicalLeft); 上面它会先判断当前行剩余空间是否小于浮动元素的宽度,如果是的话就一直往下挪。 通过上面的层层计算,就可以拿到位置坐标和具体大小,上面两个浮动的div***计算的结果是: [LayoutBlockFlow.cpp(1475)] location is: "0", "0" size is "77.3281", "18" [LayoutBlockFlow.cpp(1475)] location is: "681", "0" size is "100", "16" 有了这些信息,结合颜色等style,就可进行Paint了。 3. Paint Paint又是一块很块很复杂的东西,试图在一篇文章里面讲明layout都已经是一件不太可能的事情。 Paint的初始化会使用layout的数据,如下面的BoxPainter的构造函数: BoxPainter(const LayoutBox& layoutBox) : m_layoutBox(layoutBox) {} Paint会调用最上面说的Skia的SkCanvas画: SkCanvas* canvas() { return m_canvas; } 这个SkCanvas和JS里面的canvas有什么联系和区别? Blink JS里的canvas就是这个canvas,当在js里面获取canvas对象进行描绘时: var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d");ctx.fillStyle = "green";ctx.fillRect(10, 10, 100, 100) 就会去获取SkCanvas实例。 SkCanvas* HTMLCanvasElement::drawingCanvas() const { return buffer() ? m_imageBuffer->Canvas (): nullptr;}
So whether you draw with html/css or canvas, they are of the same origin, but the difference is that it is more intuitive and simple with the help of html/css, and the browser helps you to layout. Using canvas directly, you have to draw a little bit from the point, line and surface, but at the same time it is more flexible.
4. Trigger layout
When will layout be triggered? the above analysis has already mentioned that layout will be triggered when the html fragment is parsed. In the previous article, it was also mentioned that layout will also be triggered after loading css, and layout will also be triggered when resize pages are loaded. Because the calculation of layout is more complex, the number of layout should be reduced. For example, CSS should be written in the head tag, otherwise it will be written in body, and once a new CSS is encountered, it will be re-layout.
The second is that when obtaining dimensional information such as clientWidth/scrollTop, it is generally said that layout will be triggered to obtain values, but layout will not be triggered under the author's observation:
Using getComputedStyle or getting clientWidth as follows should trigger layout:
Var style = window.getComputedStyle (document.getElementById ("body")); var width = style.width;console.log (document.getElementById ("canvas") .clientWidth)
However, whether I break the point or hit log, I cannot observe the trigger of layout. Instead, I go directly to get the value of clientWidth:
LayoutUnit LayoutBox::clientWidth () const {return m_frameRect.width ()-borderLeft ()-borderRight ()-verticalScrollbarWidth ();}
However, layout is bound to be triggered when changing its clientWidth:
Document.getElementById ("canvas") .style.width = "500px"
The Log printed in the function of layout:
The first few lines recalculate CSS, and the next few lines do layout.
Another thing to note is to reduce the scope of the layout as much as possible, such as the demo-- that releases the menu when the menu button is clicked:
# menu, # show-btn {display: none;}. Show-menu # menu, .show-menu # show-btn {display: block;} Menu document.getElementById ("menu"). Onclick = function () {document.body.addClass ("show-menu");}
For convenience, the above adds a class to body to control the state of the menu. But this will be a big problem, because adding a class to body causes it to recalculate style and layout, and once it has layout, its child elements also follow layout, which means the entire page has to be re-layout. So the price is very high, and we should narrow the scope of influence. Therefore, it would be nice to add the class of show-menu to the directly related elements.
At this point, the entire page rendering process has been introduced. We have analyzed the process step by step from html-> CSS-> layout-> paint. Although it is not very comprehensive, we have analyzed the core process. Since writing html/css is opaque, I have no idea how it works behind it. I can only read the document to say how this tag is used and what effect that attribute will have, and then see the effect on the browser, which is a bit of a feeling of being slaughtered by the browser. So the purpose of this source code interpretation is to be able to peek into the working principle behind the browser, so that it will be helpful to write the code and be able to know it. When you encounter some difficult problems, you can quickly find a solution or the direction of the solution.
For example, the author encounters a problem with Chiba, that is, when using height: calc (100%-80px), when expanding a submenu on the Safari of the phone, the menu occasionally slips. At that time, I thought it was probably because Safari miscalculated the height when expanding the menu, so that overflow: auto didn't work. So after expanding the menu, manually calculate and set up the height, and then solve the problem.
The above content is how to look at the browser layout layout from the Chrome source code. Have you learned the knowledge or skills? If you want to learn more skills or enrich your knowledge reserve, you are welcome to follow the industry information channel.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.