How to Get Sticky and Full-Bleed Elements to Play Well Together
Recently I encountered a unique layout requirement: keeping an element always fixed on the top while the page contains full screen elements. This is quite tricky to implement, so I will document my solution in case of any inaccurate needs. Especially in the logical positioning processing on small screens, it increases the difficulty.
The effect is difficult to describe in text, so I recorded a screen video to illustrate what I mean. Pay special attention to the main call to action section, that is, the section with the title "Experience Domino now".
Our goal is to display the main call to action on the right in a larger window, and other parts pass under it as the user scrolls down. In a smaller window, the call to action element must be displayed after the main hero area with the title "Start Trial".
There are two main challenges here:
- Create full-screen elements that do not conflict with sticky elements
- Avoid duplicate HTML code
Before we dive into several possible solutions (and their limitations), let's build a semantic HTML structure.
HTML structure
When building such layouts, you may tend to create duplicate call-to-action sections: one for desktop versions, the other for mobile versions, and then toggle their visibility when appropriate. This avoids the hassle of finding the perfect location in HTML and applying CSS that handles both layout requirements. I must admit that I do the same sometimes. But this time, I want to avoid duplicating my HTML code.
Another thing to consider is that we use sticky positioning for .box--sticky
element, which means it needs to be at the same level as other elements (including full screen elements) in order to work properly.
The following are the markers:
<div> <div>Hero Zone</div> <div>Adhesive zone</div> <div>Full screen area</div> <div>Full screen area</div> </div>
Create sticky elements
Creating sticky elements in a CSS grid layout is very simple. We add position: sticky
and top: 0
offsets to .box--sticky
element, indicating where it starts pasting. Note that we only make the elements sticky when the window width is greater than 768px.
@media screen and (min-width: 768px) { .box--sticky { position: sticky; top: 0; } }
It should be noted that when position: sticky
is used with overflow: auto
, there are known sticky positioning problems in Safari. This is explained in the Known Issues section of the Caniuse website:
Setting to
overflow: auto
's parent element will preventposition: sticky
in Safari from working.
Not bad, it's easy. Next, let's solve the challenge of full-screen elements.
Solution 1: Pseudo-element
The first solution is the method I often use: absolutely positioned pseudo-elements that can extend from one side to the other. The trick here is to use negative offsets.
If we are talking about centering, the calculation is quite simple:
.box--bleed { max-width: 600px; margin-right: auto; margin-left: auto; padding: 20px; position: relative; } .box--bleed::before { content: ""; background-color: dodgerblue; position: absolute; top: 0; bottom: 0; right: calc((100vw - 100%) / -2); left: calc((100vw - 100%) / -2); }
In short, the negative offset is the window width ( 100vw
) minus the element width ( 100%
) and then divide by -2 because we need two negative offsets.
It should be noted that there is a known bug when using 100vw
, and the Caniuse website also explained this:
Currently all browsers except Firefox are mistakenly thinking that
100vw
is the entire page width, including the vertical scroll bar, which can result in horizontal scroll bars whenoverflow: auto
is set.
Now let's create full screen elements without the content being centered. If you watch the video again, you will notice that there is no content under the sticky element. We don't want sticky elements to overlap with content, which is why there is no centered content in this particular layout.
First, we will create the grid:
.grid { display: grid; grid-gap: var(--gap); grid-template-columns: var(--cols); max-width: var(--max-width); margin-left: auto; margin-right: auto; }
We use custom properties that allow us to redefine the maximum width, gap, and grid columns without redeclaring the properties. In other words, we redeclare the variable values instead of redeclaring grid-gap
, grid-template-columns
and max-width
properties:
:root { --gap: 20px; --cols: 1fr; --max-width: calc(100% - 2 * var(--gap)); } @media screen and (min-width: 768px) { :root { --max-width: 600px; --aside-width: 200px; --cols: 1fr var(--aside-width); } } @media screen and (min-width: 980px) { :root { --max-width: 900px; --aside-width: 300px; } }
On a window with a width of 768px and above, we define two columns: one with a fixed width ( --aside-width
), the other with a remaining space ( 1fr
), and the maximum width of the grid container ( --max-width
).
On windows less than 768px, we define a column and gap. The maximum width of the grid container is 100% of the viewport, minus the gap on both sides.
Now is the wonderful part. The content is not centered on the larger window, so the calculation is not as simple as you think. Here's what it looks like:
.box--bleed { position: relative; z-index: 0; } .box--bleed::before { content: ""; display: block; position: absolute; top: 0; bottom: 0; left: calc((100vw - (100% var(--gap) var(--aside-width))) / -2); right: calc(((100vw - (100% - var(--gap) var(--aside-width))) / -2) - (var(--aside-width))); z-index: -1; }
Instead of using 100% of the parent element width, we consider the gap and the width of the sticky element. This means that the content width in the full screen element does not exceed the boundary of the hero element. This way, we ensure that the sticky elements do not overlap with any important information.
The left offset is relatively simple because we only need to subtract the element width ( 100%
), gap ( --gap
) and sticky elements ( --aside-width
) from the window width ( 100vw
).
left: (100vw - (100% var(--gap) var(--aside-width)) / -2);
The right offset is more complicated because we have to add the width of the sticky element to the previous calculation, --aside-width
, and gap, --gap
:
right: ((100vw - (100% var(--gap) var(--aside-width))) / -2) - (var(--aside-width) var(--gap));
Now we can make sure that the sticky element does not overlap anything in the full screen element.
Here is a solution that contains horizontal bugs:
Here is a solution that includes horizontal bug fixes:
The fix is to hide the body's x-axis overflow, which is usually a good idea:
body { max-width: 100%; overflow-x: hidden; }
This is a completely feasible solution and we can end there. But what's the fun of this? There is usually more than one way to accomplish something, so let's look at another.
Solution 2: Fill calculation
Instead of using centered mesh containers and pseudo-elements, we can achieve the same effect by configuring the mesh. Let's start by defining the grid, like we did last time:
.grid { display: grid; grid-gap: var(--gap); grid-template-columns: var(--cols); }
Similarly, we use custom properties to define gaps and template columns:
:root { --gap: 20px; --gutter: 1px; --cols: var(--gutter) 1fr var(--gutter); }
We display three columns on windows smaller than 768px. The middle column takes up as much space as possible, while the other two columns are only used for forced horizontal gaps.
@media screen and (max-width: 767px) { .box { grid-column: 2 / -2; } }
Note that all grid elements are placed in the middle column.
On windows larger than 768px, we define a --max-width
variable that limits the width of the inner column. We also define --aside-width
, which is the width of the sticky element. Again, this ensures that the sticky element does not position over anything within the full screen element.
:root { --gap: 20px; } @media screen and (min-width: 768px) { :root { --max-width: 600px; --aside-width: 200px; --gutter: calc((100% - (var(--max-width))) / 2 - var(--gap)); --cols: var(--gutter) 1fr var(--aside-width) var(--gutter); } } @media screen and (min-width: 980px) { :root { --max-width: 900px; --aside-width: 300px; } }
Next, we will calculate the margin width. The calculation formula is:
--gutter: calc((100% - (var(--max-width))) / 2 - var(--gap));
… 100%
of which is the window width. First, we subtract the maximum width of the inner column from the window width. We then divide the result by 2 to create the margin. Finally, we subtract the grid gap to get the correct width of the margin column.
Now let's push .box--hero
element aside so that it starts with the first column of the grid:
@media screen and (min-width: 768px) { .box--hero { grid-column-start: 2; } }
This will automatically push the sticky box so it follows the hero element. We can also clearly define the position of the sticky box as follows:
.box--sticky { grid-column: 3 / span 1; }
Finally, let's create a full screen element by setting grid-column
to 1 / -1
. This tells the element to start the content from the first grid item and spans to the last grid item.
@media screen and (min-width: 768px) { .box--bleed { grid-column: 1 / -1; } }
To center content, we will calculate the left and right padding. The left side fill is equal to the size of the margin column plus the grid gap. The right fill is equal to the size of the left fill, plus another grid gap and the width of the sticky element.
@media screen and (min-width: 768px) { .box--bleed { padding-left: calc(var(--gutter) var(--gap)); padding-right: calc(var(--gutter) var(--gap) var(--gap) var(--aside-width)); } }
Here is the final solution:
I prefer this solution because it does not use the window unit in question.
I like CSS calculations. Using math is not always straightforward, especially when combining different units, such as 100%
. Finding out what 100%
means is half the job.
I also like to use CSS to solve simple but complex layouts, like this one. Modern CSS has native solutions—such as mesh, sticky positioning and computing—eliminate complex and rather heavy JavaScript solutions. Let's leave the dirty work to the browser!
Do you have a better solution or a different approach to this? I'd love to hear what you think.
The above is the detailed content of How to Get Sticky and Full-Bleed Elements to Play Well Together. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics











I see Google Fonts rolled out a new design (Tweet). Compared to the last big redesign, this feels much more iterative. I can barely tell the difference

Have you ever needed a countdown timer on a project? For something like that, it might be natural to reach for a plugin, but it’s actually a lot more

Everything you ever wanted to know about data attributes in HTML, CSS, and JavaScript.

When the number of elements is not fixed, how to select the first child element of the specified class name through CSS. When processing HTML structure, you often encounter different elements...

Questions about purple slash areas in Flex layouts When using Flex layouts, you may encounter some confusing phenomena, such as in the developer tools (d...

How to implement Windows-like in front-end development...

Tartan is a patterned cloth that’s typically associated with Scotland, particularly their fashionable kilts. On tartanify.com, we gathered over 5,000 tartan

At the start of a new project, Sass compilation happens in the blink of an eye. This feels great, especially when it’s paired with Browsersync, which reloads
