文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>WebKit 渲染系列1-2-3-4

WebKit 渲染系列1-2-3-4

时间:2010-08-30  来源:chao_yu

一. WebCore Rendering I – The Basics

   This is the first of a series of posts designed to help people interested in hacking on WebCore’s rendering system. I’ll be posting these articles as I finish them on this blog, and they will also be available in the documentation section of the Web site.

1. The DOM Tree

  A Web page is parsed into a tree of nodes called the Document Object Model (DOM for short). The base class for all nodes in the tree is Node.

Node.h

  Nodes break down into several categories. The node types that are relevant to the rendering code are:

Document – The root of the tree is always the document. There are three document classes, Document, HTMLDocument and SVGDocument. The first is used for all XML documents other than SVG documents. The second applies only to HTML documents and inherits from Document.
  The third applies to SVG documents and also inherits from Document.

Document.h  HTMLDocument.h

Elements – All of the tags that occur in HTML or XML source turn into elements. From a rendering perspective, an element is a node with a tag name that can be used to cast to a specific subclass that can be queried for data that the renderer needs.

Element.h

Text – Raw text that occurs in between elements gets turned into text nodes. Text nodes store this raw text, and the render tree can query the node for its character data.

Text.h

2. The Render Tree

  At the heart of rendering is the render tree. The render tree is very similar to the DOM in that it is a tree of objects, where each object can correspond to the document, elements or text nodes. The render tree can also contain additional objects that have no corresponding DOM node.

  The base class of all render tree nodes is RenderObject.

RenderObject.h

  The RenderObject for a DOM node can be obtained using the renderer() method on Node.

RenderObject* renderer() const

  The following methods are most commonly used to walk the render tree.

RenderObject* firstChild() const;

RenderObject* lastChild() const;

RenderObject* previousSibling() const;

RenderObject* nextSibling() const;

  Here is an example of a loop that walks a renderer’s immediate children. This is the most common walk that occurs in the render tree code.

for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {

    ...

}

3. Creating the Render Tree

  Renderers are created through a process on the DOM called attachment. As a document is parsed and DOM nodes are added, a method called attach gets called on the DOM nodes to create the renderers.

void attach()

  The attach method computes style information for the DOM node. If the display CSS property for the element is set to none or if the node is a descendant of an element with display: none set, then no renderer will be created. The subclass of the node and the CSS display property value are used together to determine what kind of renderer to make for the node.

Attach is a top down recursive operation. A parent node will always have its renderer created before any of its descendants will have their renderers created.

4. Destroying the Render Tree

  Renderers are destroyed when DOM nodes are removed from the document or when the document gets torn down (e.g., because the tab/window it was in got closed). A method called detach gets called on the DOM nodes to disconnect and destroy the renderers.

void detach()

  Detachment is a bottom up recursive operation. Descendant nodes will always have their renderers destroyed before a parent destroys its renderer.

5. Accessing Style Information

  During attachment the DOM queries CSS to obtain style information for an element. The resultant information is stored in an object called a RenderStyle.

RenderStyle.h

  Every single CSS property that WebKit supports can be queried via this object. RenderStyles are reference counted objects. If a DOM node creates a renderer, then it connects the style information to that renderer using the setStyle method on the renderer.

void setStyle(RenderStyle*)

The renderer adds a reference to the style that it will maintain until it either gets a new style or gets destroyed.

The RenderStyle can be accessed from a RenderObject using the style() method.

RenderStyle* style() const

6. The CSS Box Model

  One of the principal workhorse subclasses of RenderObject is RenderBox. This subclass represents objects that obey the CSS box model. These include any objects that have borders, padding, margins, width and height. Right now some objects that do not follow the CSS box model (e.g., SVG objects) still subclass from RenderBox. This is actually a mistake that will be fixed in the future through refactoring of the render tree.

      This diagram from the CSS2.1 spec illustrates the parts of a CSS box. The following methods can be used to obtain the border/margin/padding widths. The RenderStyle should not be used unless the intent is to look at the original raw style information, since what is actually computed for the RenderObject could be very different (especially for tables, which can override cell padding and have collapsed borders between cells).

int marginTop() const;

int marginBottom() const;

int marginLeft() const;

int marginRight() const;

 

int paddingTop() const;

int paddingBottom() const;

int paddingLeft() const;

int paddingRight() const;

 

int borderTop() const;

int borderBottom() const;

int borderLeft() const;

int borderRight() const;

The width() and height() methods give the width and height of the box including its borders.

int width() const;

int height() const;

The client box is the area of the box excluding borders and scrollbars. Padding is included.

int clientLeft() const { return borderLeft(); }

int clientTop() const { return borderTop(); }

int clientWidth() const;

int clientHeight() const;

  The term content box is used to describe the area of the CSS box that excludes the borders and padding.

IntRect contentBox() const;

int contentWidth() const { return clientWidth() - paddingLeft() - paddingRight(); }

int contentHeight() const { return clientHeight() - paddingTop() - paddingBottom(); }

  When a box has a horizontal or vertical scrollbar, it is placed in between the border and the padding. A scrollbar’s size is included in the client width and client height. Scrollbars are not part of the content box. The size of the scrollable area and the current scroll position can both be obtained from the RenderObject. I will cover this in more detail in a separate section on scrolling.

int scrollLeft() const;

int scrollTop() const;

int scrollWidth() const;

int scrollHeight() const;

  Boxes also have x and y positions. These positions are relative to the ancestor that is responsible for deciding where this box should be placed. There are numerous exceptions to this rule, however, and this is one of the most confusing areas of the render tree.

int xPos() const;

int yPos() const;

 二. WebCore Rendering II – Blocks and Inlines

   In the previous entry I talked about the basic structure of a CSS box. In this article I’m going to talk about subclasses of RenderBox and about the concepts of block and inline.

  A block flow is a box designed either to contain lines (e.g., a paragraph) or to contain other blocks that it stacks vertically. Example block flow elements in HTML are p and div.

An inline flow is an object that is designed to be part of a line. Example inline flow elements in HTML are a, b, i and span.

In WebCore, there are three renderer classes that cover block and inline flows. RenderBlock, RenderInline and their common superclass RenderFlow.

RenderFlow.h  RenderBlock.h RenderInline.h

  An inline flow can be changed to a block flow (and vice versa) using the CSS display property.

div { display: inline }  span { display: block }

  In addition to block and inline flows, there is another kind of element that can act as a block or inline: the replaced element. A replaced element is an element whose rendering is unspecified by CSS. How the contents of the object render is left up to the element itself. Examples of replaced elements are images, form controls, iframes, plugins and applets.

A replaced element can also be either block-level or inline-level. When a replaced element acts as a block, it will get stacked vertically as though it represents its own paragraph. When a replaced element acts as an inline, it will be part of a line inside a paragraph. Replaced elements are inline by default.

  Form controls are actually a strange special case. They are still replaced elements, but because they are implemented by the engine, controls actually ultimately subclass from RenderBlock. As a result, the concept of being replaced can’t really be confined to a single common subclass, and is therefore represented as a bit on RenderObject instead. The isReplaced method can be used to ask if an object is a replaced element.

bool isReplaced() const

  Images, plugins, frames and applets all inherit from a common subclass that implements replaced element behavior. This class is RenderReplaced.

RenderReplaced.h 

1. The Inline Block

  One of the most confusingly named objects in CSS is the inline-block. Inline blocks are block flows that are designed to sit on a line. In effect they are like inline replaced elements on the outside, but on the inside they are block flows. The display property in CSS can be used to create inline blocks. Inline blocks will report true if asked if they are replaced.

div { display: inline-block }

2. Tables

  Tables in HTML are block-level by default. However they can also be made into inlines using the CSS display property with a value of inline-table.

table { display: inline-table }

  Again, from the outside an inline-table is like an inline replaced element (and will return true from isReplaced), but on the inside the object is still just a table.

  In WebCore the RenderTable class represents a table. It inherits from RenderBlock for reasons that will be covered in the positioning section later.

RenderTable.h

3. Text

Raw text is represented using the RenderText class. Text is always considered inline by WebCore, since it is always placed on lines.

RenderText.h

4. Getting Block and Inline Information

  The most basic method for obtaining block vs. inline status is the isInline function. This method asks if an object is designed to be part of a line. It does not care what the interior of the element is (e.g., text, image, an inline flow, an inline-block or an inline-table).

bool isInline() const

  One of the common mistakes people make when working with the render tree is assuming that isInline means an object is always an inline flow, text or an inline replaced element. However because of inline-blocks and inline-tables, this method can return true even for these objects.

  To ask if an object is actually a block or inline flow, the following methods should be used.

bool isInlineFlow() const

bool isBlockFlow() const

  These methods are essentially asking questions about the interior of the object. An inline-block for example is still a block flow and not an inline flow. It is inline on the outside, but on the inside it is a block flow.

  The exact class type can be queried for blocks and inlines using the following methods.

bool isRenderBlock() const

bool isRenderInline() const

  The isRenderBlock method is useful in the context of positioning, since both block flows and tables act as positioned object containers.

  To ask if an object is specifically an inline block or inline table, the isInlineBlockOrInlineTable method can be used.

bool isInlineBlockOrInlineTable() const

5. Children of Block Flows

  Block flows have a simple invariant regarding their children that the render tree always obeys. That rule can be summarized as follows:

  All in-flow children of a block flow must be blocks, or all in-flow children of a block flow must be inlines.

  In other words, once you exclude floating and positioned elements, all of the children of a block flow in the render tree must return true from isInline or they must all return false from isInline. The render tree will change its structure as needed to preserve this invariant.

  The childrenInline method is used to ask whether the children of a block flow are inlines or blocks.

bool childrenInline() const

Children of Inline Flows

  Children of inline flows have an even simpler invariant that must be maintained.

All in-flow children of an inline flow must be inlines.

6. Anonymous Blocks

  In order to preserve the block flow child invariant (only inline children or only block children), the render tree will construct objects called anonymous blocks. Consider the following example:

<div>

Some text

<div>

Some more text

</div>

</div>

  In the above example, the outer div has two children: some text and another div. The first child is an inline, but the second child is a block. Because this combination of children violates the all-inline or all-block child rule, the render tree will construct an anonymous block flow to wrap the text. The render tree therefore becomes:

<div>

<anonymous block>

Some text

</anonymous block>

<div>

Some more text

</div>

</div>

  The isAnonymousBlock method can be used to ask if a renderer is an anonymous block flow.

bool isAnonymousBlock() const

  Whenever a block flow has inline children and a block object suddenly tries to insert itself as a child, anonymous blocks will be created as needed to wrap all of the inlines. Contiguous inlines will share a single common anonymous block, so the number of anonymous blocks can be kept to a minimum. The makeChildrenNonInline method in RenderBlock is the function that performs this adjustment.

void makeChildrenNonInline(RenderObject *insertionPoint)

7. Blocks inside Inline Flows

One of the nastiest constructs you will see in HTML is when a block is placed inside an inline flow. Here is an example:

<i>Italic only <b>italic and bold

<div>

Wow, a block!

</div>

<div>

Wow, another block!

</div>

More italic and bold text</b> More italic text</i>

  The two divs violate the invariant that all of the children of the bold element must be inlines. The render tree has to perform a rather complicated series of steps in order to fix up the tree. Three anonymous blocks are constructed. The first block holds all of the inlines that come before the divs. The second anonymous block holds the divs. The third anonymous block holds all of the inlines that come after the divs.

<anonymous pre block>

<i>Italic only <b>italic and bold</b></i>

</anonymous pre block>

<anonymous middle block>

<div>

Wow, a block!

</div>

<div>

Wow, another block!

</div>

</anonymous middle block>

<anonymous post block>

<i><b>More italic and bold text</b> More italic text</i>

</anonymous post block>

  Notice that the bold and italic renderers had to split into two render objects, since they are in both the anonymous pre block and the anonymous post block. In the case of the bold DOM element, its children are in the pre block, but then continue into the middle block and then finally continue into the post block. The render tree connects these objects via a continuation chain.

RenderFlow* continuation() const

bool isInlineContinuation() const

  The first bold renderer in the pre block is the one that can be obtained from the b DOM element using the element’s renderer() method. This renderer has as its continuation() the middle anonymous block. The middle anonymous block has as its continuation() the second bold renderer. In this way code that needs to examine the renderers that represent the children of the DOM element can still do so relatively easily.

  In the above example the i DOM element also split. Its children are all in the pre and post blocks, and therefore only one connection is needed. The italic renderer in the pre block sets its continuation() to the italic renderer in the post block.

The function that performs the recursive splitting of inline flows and that creates the continuation chain connections is called splitFlow and is in RenderInline.cpp.

三. WebCore Rendering III – Layout Basics

  When renderers are first created and added to the tree, they have no position or size yet. The process by which all of the boxes have their positions and sizes determined is called layout. All renderers have a layout method.

void layout()

  Layout is a recursive operation. A class called FrameView represents the containing view for the document, and it also has a layout method. The frame view is responsible for managing the layout of the render tree.

  There are two types of layout that the FrameView can perform. The first (and by far the most common) is a layout of the entire render tree. In this case the root of the render tree has its layout method called and then the entire render tree gets updated. The second type of layout is constrained to a specific subtree of the render tree. It is used in situations where the layout of some small subtree can’t possibly affect its surroundings. Right now the subtree layout is only used by text fields (but may be used by overflow:auto blocks and other similar constructs in the future).

1. The Dirty Bits

  Layout uses a dirty bit system to determine whether an object actually needs a layout. Whenever new renderers are inserted into the tree, they dirty themselves and the relevant links in their ancestor chain. There are three unique bits that are used by the render tree.

bool needsLayout() const { return m_needsLayout || m_normalChildNeedsLayout || m_posChildNeedsLayout; }

bool selfNeedsLayout() const { return m_needsLayout; }

bool posChildNeedsLayout() const { return m_posChildNeedsLayout; }

bool normalChildNeedsLayout() const { return m_normalChildNeedsLayout; }

The first bit is used when the renderer itself is dirty, and it can be queried using the method selfNeedsLayout. Whenever this bit is set to true, relevant ancestor renderers have bits set indicating that they have a dirty child. The type of bit set depends on the positioned status of the previous link in the chain being dirtied. posChildNeedsLayout is used to indicate that a positioned child got dirtied. normalChildNeedsLayout is used to indicate that an in-flow child got dirtied. By distinguishing between these two types of children, layout can be optimized for the case where only positioned elements moved.

2. The Containing Block

  What exactly did I mean by “the relevant ancestor chain”? When an object is marked as needing layout, the ancestor chain that is dirtied is based on a CSS concept called the containing block. The containing block is also used to establish the coordinate space of children. Renderers have xPos and yPos coordinates, and these are relative to their containing blocks. So what exactly is a containing block?

Here is the CSS 2.1 spec’s introduction to the concept.

  My own way of introducing the concept in terms of the WebCore render tree would be as follows:

  A renderer’s containing block is an ancestor block of the renderer that is responsible for determining that renderer’s position.

In other words when layout recurs down the render tree, it is a block’s responsibility to position all of the renderers that have it as their containing block.

  The root of the render tree is called the RenderView, and this class corresponds to the initial containing block according to CSS2.1. It is also the renderer that will be returned if the renderer() method is called on the Document.

RenderView.h

  The initial containing block is always sized to the viewport. In desktop browsers, this is the visible area in the browser window. It is also always at position (0,0) relative to the entire document. Here’s a picture illustrating where the initial containing block is positioned for a document. The black bordered box represents the RenderView, and the grey box represents the entire document.

If the document is scrolled, then the initial containing block will be moved offscreen. It is always at the top of the document and sized to the viewport. One area of confusion that people often have with the initial containing block is that they expect it to somehow be outside the document and part of the viewport.

Here is the detailed containing block specification in CSS2.1.

  The rules can be summarized as follows:

  The root element’s renderer (i.e., the <html> element) will always have the RenderView as its containing block.

  If a renderer has a CSS position of relative or static, then the containing block will be the nearest block-level ancestor in the render tree.

  If a renderer has a CSS position of fixed, then the containing block will be the RenderView. Technically the RenderView does not act as a viewport, so the RenderView has to adjust the coordinates of fixed positioned objects to account for the document scroll position. It is simpler to just let the RenderView act like a viewport containing block for this one case rather than having a separate renderer just for the viewport.

  If a renderer has a CSS position of absolute, then the containing block is the nearest block-level ancestor with a position other than static. If no such ancestor exists, then the containing block will be the RenderView.

  The render tree has two convenience methods for asking if an object has a position of absolute, fixed or relative. They are:

bool isPositioned() const;   // absolute or fixed positioning

bool isRelPositioned() const;  // relative positioning  

  Throughout most of the code the term positioned refers to both absolute and fixed positioned objects in CSS. The term relPositioned refers to relative positioned objects in CSS.

  The render tree has a method for obtaining the containing block of a renderer.

RenderBlock* containingBlock() const  

  When an object is marked as needing layout, it walks up a container chain setting either the normalChildNeedsLayout bit or the posChildNeedsLayout bit. The isPositioned status of the previous link in the chain determines what bit gets set. The container chain roughly corresponds to the containing block chain, although intermediate inlines are still dirtied as well. A different method called container is used instead of containingBlock to determine the chain for dirtying because of this distinction.

RenderObject* container() const

layoutIfNeeded and setNeedsLayout(false)

  The layoutIfNeeded method (similar in terminology to AppKit’s displayIfNeeded method) is a convenient shorthand for telling a renderer to only do a layout if it has a dirty bit set.

void layoutIfNeeded()  

  All layout methods typically end with setNeedsLayout(false). It is important to clear dirty bits on the renderer before leaving the layout method, so that future layout calls won’t mistakenly think that the object is still dirty.

3. Anatomy of a Layout Method

At a high level layout methods usually look something like this:

void layout()

{

    ASSERT(needsLayout());

     // Determine the width and horizontal margins of this object.

    ...

     for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {

        // Determine if the child needs to get a relayout despite the dirty bit not being set.

        ...

         // Place the child.

        ...

         // Lay out the child

        child->layoutIfNeeded();

        ...

    }

     // Now the intrinsic height of the object is known because the children are placed

    // Determine the final height

    ...

     setNeedsLayout(false);

}

We will drill into specific layout methods in future articles.

 四. WebCore Rendering IV – Absolute/Fixed and Relative Positioning

  The position property in CSS can be used to position an object relative to a specific containing block. It has four values: ’static’, ‘absolute’, ‘fixed’ and ‘relative’. Static positioning is the default and means that the object is just positioned using the normal rules of block and line layout.

Relative Positioning

Relative positioning is exactly like static positioning except that the CSS left, top, right and bottom properties can be used to apply a translation to the object. The isRelPositioned method can be used to ask if a renderer is using relative positioning.

bool isRelPositioned() const

The translation offset that will be applied can be obtained using the following methods:

int relativePositionOffsetX() const;

int relativePositionOffsetY() const;

Relative positioning is literally nothing more than a paint-time translation. As far as layout is concerned, the object is at its original position. Below is an example that uses relative positioning to shift part of a line up by a few pixels. As you can see, the line lays out as though the object was at the original position.

<div style="border:5px solid black; padding:20px; width:300px">

Here is a line of text.

<span style="position:relative;top:-10px; background-color: #eeeeee">

This part is shifted<br> up a bit,

</span>

but the rest of the line is in its original position.

</div>

Here is a line of text. This part is shifted
up a bit, but the rest of the line is in its original position.

Absolute and Fixed Positioning

Fixed positioned objects are positioned relative to the viewport, i.e., the visible page area of your browser window. Absolute positioned objects are positioned relative to the containing block, which is the nearest enclosing ancestor block with a position other than ’static’. If no such block exists, then the initial containing block (the RenderView) is used. For more details on containing blocks, see the previous article.

The isPositioned method can be used to find out if a renderer is absolute or fixed positioned.

bool isPositioned() const

When an object is absolute/fixed positioned, it becomes block-level. Even if the CSS display type is set to inline (or inline-block/table), the effective display type becomes block-level once an object is positioned. The isInline method will return false for positioned elements.

The RenderStyle can give both display values. There are times where the original display type is relevant and needed by layout, and the following methods can be used to obtain both display types.

EDisplay display() const;

EDisplay originalDisplay() const;

The Positioned Objects List

Every block has a positioned objects list that contains all of the absolute/fixed positioned renderers for which it is the containing block. It is the block’s responsibility to place these positioned children. The following methods can be used to manipulate the positioned objects list:

void insertPositionedObject(RenderObject*);

void removePositionedObject(RenderObject*);

The layoutOnlyPositionedObjects method is used to lay out only the positioned objects for a block. If only positioned objects changed, then this method returns true. This indicates that the layout method doesn’t have to lay out any of its normal children and can just return early.

bool layoutOnlyPositionedObjects

The layoutPositionedObjects method takes care of the placement of positioned objects. It takes a boolean argument that indicates whether relayout should be forced on all of the objects. Relayout can be necessary under a variety of circumstances that will be covered in future articles.

bool layoutPositionedObjects(bool relayoutChildren)

Coordinates of Positioned Objects

The coordinates of positioned objects in CSS are relative to the padding edge of the containing block. For example specifying a left and top position of (0, 0) for an absolute positioned object will result in the object being placed just inside the containing block’s top left border. Here is an example:

<div style="position:relative;border:5px solid black;width:300px;">

<div style="position:absolute;width:200px;background-color:purple"></div>

</div>

In WebCore, coordinate positions are always relative to the border edge, so in the above example, the object is actually at (5, 5).

When the desired coordinates are omitted from CSS, WebCore has to determine an appropriate place for the positioned object. CSS has a set of extremely complex rules for this, which we will delve into in more detail in future articles.

Inline Relative Positioned Containers

It is possible for a relative positioned inline to act as a “containing block” for an absolutely positioned descendant. This is another extremely complex edge case that warrants its own article. For now it is enough to know that such a construct is possible, and that the code does have to deal with it.

WebCore Rendering V – Floats

A float is a renderer that is designed to shift all the way to the left side or all the way to the right side of a paragraph. The lines of the paragraph then flow around the floating object avoiding it. You can see an example of a float in this very paragraph. There is a purple box in the upper right hand corner. Note how all of the text in this paragraph is avoiding the float.

Here is how the purple float above was declared:

<div style="float:right; width:50px; background-color:purple; margin-left: 5px"></div>

There are also HTML constructs that imply CSS floating behavior. For example, the align attribute on the img element can be used to float an image.

<img align=left src="...">

A float can span multiple paragraphs. In this example, even though the float was declared inside this paragraph, it is tall enough that it will protrude out of this paragraph and into the next one.

Because floats can effectively intersect multiple blocks, WebCore uses a floating objects list on block flows to track all of the floating renderers that intrude into the bounds of that block. A single float can therefore be in the floating objects lists of multiple block flows. Line layout has to be aware of the positions of floats so that it can make sure to shrink lines as necessary to avoid these floats. By having all of the relevant floats for a given block immediately accessible in this floating objects list, it becomes easy for that block to know where the floats are that it needs to avoid.

A floating objects list contains the following data structure:

    struct FloatingObject {

        enum Type {

            FloatLeft,

            FloatRight

        };

 

        FloatingObject(Type type)

            : node(0)

            , startY(0)

            , endY(0)

            , left(0)

            , width(0)

            , m_type(type)

            , noPaint(false)

        {

        }

        Type type() { return static_cast<type>(m_type); }

        RenderObject* node;

        int startY;

        int endY;

        int left;

        int width;

        unsigned m_type : 1; // Type (left or right aligned)

        bool noPaint : 1;

    };

As you can see, this structure effectively contains a rectangle (a top, bottom, left and width). The reason each list entry has its own position and size that is independent of the floating object’s position and size is that these coordinates are in the space of the block that owns the floating objects list. This way each block can easily query for the float’s position in its own coordinate space without having to do a bunch of conversions.

In addition the margins of the float are included in the list entry rectangles, since lines don’t just avoid the border box of the float. They avoid the margin box of the float. This is important so that floats can be padded with extra space to avoid having lines run right up to their edges.

The following methods operate on the floating objects list:

void insertFloatingObject(RenderObject*);

void removeFloatingObject(RenderObject*);

void clearFloats();

void positionNewFloats();

The first two functions are pretty self-explanatory. They are used to add and remove specific floats from a block’s floating objects list. clearFloats will delete all of the objects in a block’s floating objects list.

When an object gets inserted into the list it is unpositioned initially. Its vertical position is set to -1. The positionNewFloats method is called by layout to place all of the floats. CSS has a bunch of rules for where floats are allowed to go. It is this method that ensures that all of those rules are obeyed.

There are many more helper methods for working with floats. I will cover these when I talk about block and line layout in more detail in future articles.

Clearance

CSS provides a way for objects to specify that they should be below either all left floats, all right floats, or all floats. The clear property specifies this behavior and has values of none, left, right or both.

This paragraph is below the blue float from the previous paragraph because it specified clear: left. Clearance is computed and applied during block and line layout. The clear property can also be applied to floats to make sure that a float ends up below all previous floats (again, either left, right or both types of floats).

 

相关阅读 更多 +
排行榜 更多 +
盒子小镇2游戏手机版下载

盒子小镇2游戏手机版下载

冒险解谜 下载
世界盒子模组版下载最新版本

世界盒子模组版下载最新版本

模拟经营 下载
音乐搜索app最新版本下载

音乐搜索app最新版本下载

趣味娱乐 下载