OLD | NEW |
(Empty) | |
| 1 # Block Layout # |
| 2 |
| 3 This document can be viewed in formatted form [here](https://chromium.googlesour
ce.com/chromium/src/+/master/third_party/WebKit/Source/core/layout/ng/BlockLayou
t.md). |
| 4 |
| 5 ## Floats ## |
| 6 |
| 7 TODO. |
| 8 |
| 9 ## An introduction to margin collapsing ## |
| 10 |
| 11 A simple way to think about [margin collapsing](https://www.w3.org/TR/CSS2/box.h
tml#collapsing-margins) |
| 12 is that it takes the maximum margin between two elements. For example: |
| 13 |
| 14 ```html |
| 15 <!-- The divs below are 20px apart --> |
| 16 <div style="margin-bottom: 10px;">Hi</div> |
| 17 <div style="margin-top: 20px;">there</div> |
| 18 ``` |
| 19 |
| 20 This is complicated by _negative_ margins. For example: |
| 21 |
| 22 ```html |
| 23 <!-- The divs below are 10px apart --> |
| 24 <div style="margin-bottom: 20px;">Hi</div> |
| 25 <div style="margin-top: -10px;">there</div> |
| 26 |
| 27 <!-- The divs below are -20px apart --> |
| 28 <div style="margin-bottom: -20px;">Hi</div> |
| 29 <div style="margin-top: -10px;">there</div> |
| 30 ``` |
| 31 |
| 32 The rule here is: `max(pos_margins) + min(neg_margins)`. This rule we'll refer |
| 33 to as the _margin collapsing rule_. If this only happened between top level |
| 34 elements it would be pretty simple, however consider the following: |
| 35 |
| 36 ```html |
| 37 <!-- The top-level divs below are -2px apart --> |
| 38 <div style="margin-bottom: 3px"> |
| 39 <div style="margin-bottom: -5"> |
| 40 <div style="margin-bottom: 7px">Hi</div> |
| 41 </div> |
| 42 </div> |
| 43 <div style="margin-top: 11px"> |
| 44 <div style="margin-top: -13px">there</div> |
| 45 </div> |
| 46 ``` |
| 47 |
| 48 In the above example as there isn't **anything** separating the edges of two |
| 49 fragments the margins stack together (e.g. no borders or padding). There are |
| 50 known as **adjoining margins**. If we apply our formula to the above we get: |
| 51 `max(3, 7, 11) + min(-5, -13) = -2`. |
| 52 |
| 53 A useful concept is a **margin strut**. This is a pair of margins consisting of |
| 54 one positive and one negative margin. |
| 55 |
| 56 A margin strut allows us to keep track of the largest positive and smallest |
| 57 negative margin. E.g. |
| 58 ```cpp |
| 59 struct MarginStrut { |
| 60 LayoutUnit pos_margin; |
| 61 LayoutUnit neg_margin; |
| 62 |
| 63 void Append(LayoutUnit margin) { |
| 64 if (margin < 0) |
| 65 neg_margin = std::min(margin, neg_margin); |
| 66 else |
| 67 pos_margin = std::max(margin, pos_margin); |
| 68 } |
| 69 |
| 70 LayoutUnit Sum() { return pos_margin + neg_margin; } |
| 71 } |
| 72 ``` |
| 73 |
| 74 A naïve algorithm for the adjoining margins case would be to _bubble_ up |
| 75 margins. For example each fragment would have a **margin strut** at the |
| 76 block-start and block-end edge. If the child fragment was **adjoining** to its |
| 77 parent, you simply keep track of the margins by calling `Append` on the margin |
| 78 strut. E.g. |
| 79 |
| 80 ```cpp |
| 81 // fragment1 is the first child. |
| 82 MarginStrut s1 = fragment1.block_start_margin_strut; |
| 83 s1.Append(node1.style.margin_start); |
| 84 |
| 85 builder.SetStartMarginStrut(s1); |
| 86 |
| 87 // fragment2 is the last child. |
| 88 MarginStrut s2 = fragment2.block_end_margin_strut; |
| 89 s2.Append(node2.style.margin_start); |
| 90 |
| 91 builder.SetEndMarginStrut(s2); |
| 92 ``` |
| 93 |
| 94 When it comes time to collapse the margins you can use the margin collapsing |
| 95 rule, e.g. |
| 96 ```cpp |
| 97 MarginStrut s1 = fragment1.block_end_margin_strut; |
| 98 MarginStrut s2 = fragment2.block_start_margin_strut; |
| 99 LayoutUnit distance = |
| 100 std::max(s1.pos_margin, s2.pos_margin) + |
| 101 std::min(s1.neg_margin, s2.neg_margin); |
| 102 ``` |
| 103 |
| 104 This would be pretty simple - however it doesn't work. As we discussed in the |
| 105 floats section a _child_ will position _itself_ within the BFC. If we did margin |
| 106 collapsing this way we'd create a circular dependency between layout and |
| 107 positioning. E.g. we need to perform layout in order to determine the |
| 108 block-start margin strut, which would allow us to position the fragment, which |
| 109 would allow us to perform layout. |
| 110 |
| 111 We **invert** the problem. A fragment now only produces an _end_ margin strut. |
| 112 The _start_ margin strut becomes an input as well as where the margin strut is |
| 113 currently positioned within the BFC. For example: |
| 114 |
| 115 ```cpp |
| 116 Fragment* Layout(LogicalOffset bfc_estimate, MarginStrut input_strut) { |
| 117 MarginStrut curr_strut = input_strut; |
| 118 LogicalOffset curr_bfc_estimate = bfc_estimate; |
| 119 |
| 120 // We collapse the margin strut which allows us to compute our BFC offset if |
| 121 // we have border or padding. I.e. we don't have an adjoining margin. |
| 122 if (border_padding.block_start) { |
| 123 curr_bfc_estimate += curr_strut.Sum(); |
| 124 curr_strut = MarginStrut(); |
| 125 |
| 126 fragment_builder.SetBfcOffset(curr_bfc_estimate); |
| 127 curr_bfc_estimate += border_padding.block_start; |
| 128 } |
| 129 |
| 130 for (const auto& child : children) { |
| 131 curr_strut.Append(child.margins.block_start); |
| 132 const auto* fragment = child.Layout(curr_bfc_estimate, curr_strut); |
| 133 |
| 134 curr_strut = fragment->end_margin_strut; |
| 135 curr_strut.Append(child.margins.block_end); |
| 136 |
| 137 curr_bfc_estimate = fragment->BfcOffset() + fragment->BlockSize(); |
| 138 } |
| 139 |
| 140 fragment_builder.SetEndMarginStrut(curr_strut); |
| 141 |
| 142 return fragment_builder.ToFragment(); |
| 143 } |
| 144 ``` |
| 145 |
| 146 It isn't immediately obvious that this works, but if you try and work through an |
| 147 example manually, it'll become clearer. |
| 148 |
| 149 There are lots of different things which can "resolve" the BFC offset of an |
| 150 element. For example inline content (text, atomic inlines), border and padding, |
| 151 if a child _might_ be affected by clearance. |
| 152 |
| 153 ## Zero block-size fragments ## |
| 154 |
| 155 TODO. |
| 156 |
OLD | NEW |