{"id":8090,"date":"2025-12-24T09:41:32","date_gmt":"2025-12-24T14:41:32","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=8090"},"modified":"2025-12-24T09:41:33","modified_gmt":"2025-12-24T14:41:33","slug":"toggle-position-sticky-to-position-fixed-on-scroll","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/toggle-position-sticky-to-position-fixed-on-scroll\/","title":{"rendered":"Toggle `position: sticky` to `position: fixed` on Scroll"},"content":{"rendered":"\n<p>It&#8217;s quite an unusual look when you see an element glide along it&#8217;s parent element as <code>position: fixed;<\/code>, the slide right on out of it, as if the positoning of it somehow magically changes at just the right moment, to <code>position: sticky;<\/code>. This is exactly what we&#8217;re going to pull of here with the help of scroll-driven animation and scroll state queries.<\/p>\n\n\n\n<p>Both <code>sticky<\/code> and <code>fixed<\/code> positioning are about locking an element to a point on screen where it stays stuck throughout scrolling. A sticky element is stuck within its scrollable ancestor, and a fixed element sticks to the viewport. Both great for user interfaces that have to be persistent, like alert banners. They also make for nice visual effects.<\/p>\n\n\n\n<p>Switching between these two types of position can give the illusion of an element breaking out of its scrollable container while the user is scrolling the page. Here\u2019s an example:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_myPamxB\" src=\"\/\/codepen.io\/anon\/embed\/myPamxB?height=750&amp;theme-id=1&amp;slug-hash=myPamxB&amp;default-tab=result\" height=\"750\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed myPamxB\" title=\"CodePen Embed myPamxB\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Let\u2019s see the mechanism behind that change.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-layout\">The Layout<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"scrollPort\"<\/span>&gt;<\/span>\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"visualBlock\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"stickyElement\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n  <span class=\"hljs-comment\">&lt;!-- more blocks --&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.scrollPort<\/span> {\n  <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n\n  <span class=\"hljs-attribute\">overflow-y<\/span>: auto;\n\n  .visualBlock {\n    <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n\n    .stickyElement {\n      <span class=\"hljs-attribute\">position<\/span>: sticky;\n      <span class=\"hljs-attribute\">top<\/span>: <span class=\"hljs-number\">40px<\/span>;\n    }\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>.scrollPort<\/code> is a scroll container with a set of <code>.visualBlocks<\/code> that overflow the container. Each <code>.visualBlock<\/code> has a sticky element inside.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"sizing-the-sticky-element\">Sizing the Sticky Element<\/h2>\n\n\n\n<p>Fixed units for the dimensions of the sticky element won\u2019t be a problem, but if they have to be relative, there are some precautions to take.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.visualBlock<\/span> {\n  <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n  \n  <span class=\"hljs-attribute\">container-type<\/span>: inline-size;\n\n  .stickyElement {\n    <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n    \n    <span class=\"hljs-comment\">\/* Sets the width to 80% of the query container's (.visualBlock) width *\/<\/span>\n    <span class=\"hljs-attribute\">width<\/span>: <span class=\"hljs-number\">80<\/span>cqw;\n\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We can\u2019t use a percentage (like <code>80%<\/code>) to size the sticky element relative to its parent, because the reference element for a percentage unit is its nearest parent, which changes when the element goes from sticky to fixed*.<\/p>\n\n\n\n<p class=\"has-small-font-size\">*A fixed element\u2019s reference point in a document flow is the viewport.<\/p>\n\n\n\n<p>To use the same reference for relatively sizing the sticky element, even when it becomes fixed, <strong>use container query units<\/strong>:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Establish the <code>.visualBlock<\/code> as an <code>inline-size<\/code>* query container<\/li>\n\n\n\n<li>Use <code>cqw<\/code> unit for <code>.stickyElement<\/code>\u2019s width<\/li>\n<\/ol>\n\n\n\n<p class=\"has-small-font-size\">*In horizontal writing, the width is along the inline axis.<\/p>\n\n\n\n<p>With sizing done, we move onto the code to change the <code>position<\/code> value.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"method-1-using-scroll-driven-animation\">Method 1: Using Scroll-Driven Animation<\/h2>\n\n\n\n<p>We use CSS <code>view()<\/code> function to run a keyframe animation that\u2019ll turn <code>.stickyElement<\/code> from sticky to fixed.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.visualBlock<\/span> {\n  <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n\n  <span class=\"hljs-attribute\">--stickyPosition<\/span>: sticky;\n\n  <span class=\"hljs-attribute\">animation<\/span>: toFixed;\n  <span class=\"hljs-attribute\">animation-timeline<\/span>: <span class=\"hljs-built_in\">view<\/span>(block <span class=\"hljs-number\">0%<\/span> <span class=\"hljs-number\">100%<\/span>);\n\n\n  .stickyElement {\n    <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n    \n    <span class=\"hljs-attribute\">position<\/span>: <span class=\"hljs-built_in\">var<\/span>(--stickyPosition); <span class=\"hljs-comment\">\/* previously, position: sticky; *\/<\/span>\n  }\n}\n\n<span class=\"hljs-keyword\">@keyframes<\/span> toFixed {\n  <span class=\"hljs-selector-tag\">to<\/span> { \n    <span class=\"hljs-attribute\">--stickyPosition<\/span>: fixed; \n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The parts above:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>--stickyPosition: sticky;<\/code> \u2014 Set a CSS variable in <code>.visualBlock<\/code> with an initial value of <code>sticky<\/code>. This value is used by <code>.stickyElement<\/code> to set its <code>position<\/code>.<\/li>\n\n\n\n<li><code>animation: toFixed;<\/code> \u2014 Apply the CSS animation <code>toFixed<\/code> (explained later) to <code>.visualBlock<\/code>.<\/li>\n\n\n\n<li><code>animation-timeline: view(block 0% 100%);<\/code> \u2014 The animation&#8217;s progress is based on <code>.visualBlock<\/code>\u2019s visibility within <code>.scrollPort<\/code>. It starts when <code>.visualBlock<\/code> scrolls into view (<code>0%<\/code>) and ends (<code>100%<\/code> progress) when it scrolls out of view.<\/li>\n\n\n\n<li><code>toFixed<\/code> \u2014 At the end* (<code>to<\/code>) of the animation progress set <code>--stickyPosition<\/code> to <code>fixed<\/code>.<\/li>\n<\/ul>\n\n\n\n<p class=\"has-small-font-size\">*The <code>position<\/code> CSS property is discrete. When animated, it changes from its start to end value halfway through the animation.<\/p>\n\n\n\n<p>We\u2019re not done yet, but here\u2019s how it works when <code>toFixed<\/code> animation is applied through <code>view()<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_ZYWNmMP\" src=\"\/\/codepen.io\/anon\/embed\/ZYWNmMP?height=750&amp;theme-id=1&amp;slug-hash=ZYWNmMP&amp;default-tab=result\" height=\"750\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed ZYWNmMP\" title=\"CodePen Embed ZYWNmMP\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>A couple of things to take care of. First, when <code>.stickyElement<\/code> turns <code>fixed<\/code> it shifts slightly, since its <code>top<\/code> is no longer relative to <code>.visualBlock<\/code>. Needs reassigning the correct top value to prevent the shift.<\/p>\n\n\n\n<p>Second, <code>.stickyElement<\/code> reverts to sticky when its <code>.visualBlock<\/code> goes off-screen, which is too soon since we want it to reach the next <code>.stickyElement<\/code>. Time to expand the area tracked for the view timeline to include the space between <code>.visualBlocks<\/code> and above <code>.stickyElement<\/code>.<\/p>\n\n\n\n<p>I\u2019ll keep these values is CSS variables for ease of update.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.scrollPort<\/span> {\n  <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n\n  <span class=\"hljs-attribute\">container-type<\/span>: size;\n\n  .visualBlock {\n    <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n\n    <span class=\"hljs-attribute\">--visualBlockMargin<\/span>: <span class=\"hljs-number\">60px<\/span>;\n    <span class=\"hljs-attribute\">--stickyPosition<\/span>: sticky;\n    <span class=\"hljs-attribute\">--stickyMarginTop<\/span>: <span class=\"hljs-number\">50px<\/span>;\n    <span class=\"hljs-attribute\">--stickyTopTemp<\/span>: <span class=\"hljs-number\">40px<\/span>;\n    <span class=\"hljs-attribute\">--stickyTop<\/span>: <span class=\"hljs-built_in\">var<\/span>(--stickyTopTemp);\n\n    <span class=\"hljs-attribute\">margin<\/span>: <span class=\"hljs-built_in\">var<\/span>(--visualBlockMargin) auto; \n    <span class=\"hljs-comment\">\/* the space between .visualBlocks *\/<\/span>\n\n    <span class=\"hljs-attribute\">animation<\/span>: toFixed;\n    <span class=\"hljs-attribute\">animation-timeline<\/span>: <span class=\"hljs-built_in\">view<\/span>(block calc(-<span class=\"hljs-number\">1<\/span> * (var(--visualBlockMargin) + <span class=\"hljs-built_in\">var<\/span>(--stickyMarginTop))) <span class=\"hljs-number\">100%<\/span>);\n    <span class=\"hljs-comment\">\/* includes the space above .visualBlock and .stickyElement *\/<\/span>\n\n    .stickyElement {\n      <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n\n      <span class=\"hljs-attribute\">margin<\/span>: <span class=\"hljs-built_in\">var<\/span>(--stickyMarginTop) auto auto; \n     <span class=\"hljs-comment\">\/* the space above .stickyElement *\/<\/span>\n\n      <span class=\"hljs-attribute\">position<\/span>: <span class=\"hljs-built_in\">var<\/span>(--stickyPosition);\n      <span class=\"hljs-attribute\">top<\/span>: <span class=\"hljs-built_in\">var<\/span>(--stickyTop);\n    }\n  }\n}\n\n<span class=\"hljs-keyword\">@keyframes<\/span> toFixed {\n  <span class=\"hljs-selector-tag\">to<\/span> {\n    <span class=\"hljs-attribute\">--stickyPosition<\/span>: fixed;\n    <span class=\"hljs-attribute\">--stickyTop<\/span>: <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50vh<\/span> - <span class=\"hljs-number\">50<\/span>cqh + var(--stickyTopTemp) - <span class=\"hljs-built_in\">var<\/span>(--stickyMarginTop));\n    <span class=\"hljs-comment\">\/* includes the space above .scrollPort and .stickyElement *\/<\/span>\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"learn-more\">Negative inset values in <code>view()<\/code> expand the element&#8217;s visibility range outward from the boundary edges.<\/p>\n\n\n\n<p>Here\u2019s the result:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_myPYQQm\" src=\"\/\/codepen.io\/anon\/embed\/myPYQQm?height=750&amp;theme-id=1&amp;slug-hash=myPYQQm&amp;default-tab=result\" height=\"750\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed myPYQQm\" title=\"CodePen Embed myPYQQm\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>This is the method used in our first example, shown at the beginning of the article.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"method-2-using-scroll-state-queries\">Method 2: Using Scroll State Queries<\/h2>\n\n\n\n<p>The second method, using scroll state queries, is the most efficient way to achieve what we want. The only downside is that scroll state queries are not widely supported by browsers yet.<\/p>\n\n\n\n<p>We don\u2019t need a keyframe animation for this one. What we need is a sticky scroll state container.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"scrollPort\"<\/span>&gt;<\/span>\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"visualBlock\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"stickyWrapper\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"stickyElement\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n  <span class=\"hljs-comment\">&lt;!-- more visual blocks --&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.stickyWrapper<\/span> {\n  <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n\n  <span class=\"hljs-attribute\">container-type<\/span>: scroll-state;\n\n  <span class=\"hljs-attribute\">position<\/span>: sticky;\n  <span class=\"hljs-attribute\">--stickyTop<\/span>: <span class=\"hljs-number\">40px<\/span>;\n  <span class=\"hljs-attribute\">top<\/span>: <span class=\"hljs-built_in\">var<\/span>(--stickyTop);\n\n  .stickyElement {\n      <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n   }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"learn-more\"><strong>A scroll state container lets its<\/strong> <em><strong>descendants<\/strong><\/em> <strong>use scroll state queries to apply styles based on the container\u2019s scrolling state.<\/strong><\/p>\n\n\n\n<p>That\u2019s why we use a <code>.stickyWrapper<\/code> to provide the sticky positioning and be used as the scroll state query container.<\/p>\n\n\n\n<p>When <code>.stickyWrapper<\/code> gets stuck, we\u2019ll turn its child, <code>.stickyElement<\/code>, to fixed.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@container<\/span> scroll-state(<span class=\"hljs-attribute\">stuck:<\/span> top) {\n  <span class=\"hljs-selector-class\">.stickyElement<\/span> {\n    <span class=\"hljs-attribute\">position<\/span>: fixed;\n    <span class=\"hljs-attribute\">top<\/span>: <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">50vh<\/span> - <span class=\"hljs-number\">50<\/span>cqh + var(--stickyTop));\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here\u2019s how it looks:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_qEZGQvb\" src=\"\/\/codepen.io\/anon\/embed\/qEZGQvb?height=750&amp;theme-id=1&amp;slug-hash=qEZGQvb&amp;default-tab=result\" height=\"750\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed qEZGQvb\" title=\"CodePen Embed qEZGQvb\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>As you can see, this method <strong>requires much less code<\/strong> in CSS. But since <code>view()<\/code> is widely supported at the moment, compared to scroll state queries, it\u2019s good to have the first method available, too. Choose whichever method or design you want. The key for this to work is to simply <strong>maintain the right size and position for the element when it shifts back and forth between its sticky and fixed behavior<\/strong> to look like it\u2019s moving between the visual blocks.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"uses-and-variants\">Uses and Variants<\/h2>\n\n\n\n<p>If there\u2019s a visual element that\u2019s not to be unnecessarily shown to the user right off the bat, but <strong>once shown could be useful to keep it on screen<\/strong>, toggling its position like the examples in this post might do the trick. It could be a call-to-action button, or a banner, or it could be graphics moving between slides in a presentation once a particular slide is shown.<\/p>\n\n\n\n<p>On top of the position change, if other visual changes are layered, that opens up even more variations for how this can play out.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_ByKeGvY\" src=\"\/\/codepen.io\/anon\/embed\/ByKeGvY?height=750&amp;theme-id=1&amp;slug-hash=ByKeGvY&amp;default-tab=result\" height=\"750\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed ByKeGvY\" title=\"CodePen Embed ByKeGvY\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>As mentioned before, focus on where and how you want the element to appear when its sticky and when its fixed, for the desired effect to come through as the position changes on scroll.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Fixed and sticky positioning behave very differently, but we can switch between the two at exact points for some unusual looking effects. <\/p>\n","protected":false},"author":20,"featured_media":8097,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[7,419],"class_list":["post-8090","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-css","tag-sticky"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/12\/fixed-sticky.jpg?fit=2000%2C1200&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/8090","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/20"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=8090"}],"version-history":[{"count":4,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/8090\/revisions"}],"predecessor-version":[{"id":8100,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/8090\/revisions\/8100"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/8097"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=8090"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=8090"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=8090"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}