CSS实现flex布局固定元素 窗口缩放无需刷新效果
我已知可通过 CSS 的sticky/fixed定位属性实现滚动时元素固定在指定位置,例如下滑页面时将导航栏固定在页面顶部。
本次需要实现的交互效果:
- 导航栏(navbar)始终固定在页面顶部
- 当页面滚动到类名为
side的 div 区块位置时,navbar 和 side 元素均固定在各自对应位置,后续页面滚动时两个元素持续保持固定展示状态
由于页面主体为左右分栏的 flex 布局,无法直接给 side 元素设置 fixed 定位,因此我借助 JavaScript + jQuery 编写了实现逻辑,目前基础功能可正常运行,但存在明确异常:
窗口缩放时,元素宽度无法实时更新,始终读取初始化时的旧宽度值,导致 navbar 滚动到 side 元素位置时元素尺寸显示错乱,仅在手动刷新页面后才可恢复正常。
需要在不刷新页面的前提下,保留当前已有的固定交互效果,解决窗口缩放时的尺寸异常问题。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Position</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { margin-bottom: 1000px; } .navbar, .jumbo, .content, .side { height: 200px; width: 100%; } .navbar { background-color: lightblue; position: fixed; top: 0; left: 0; right: 0; } .jumbo { background-color: lightpink; margin-top: 200px; } .container { display: flex; } .content { background-color: red; } .side{ background-color: green; position: relative; } </style> </head> <body> <div class="navbar"></div> <div class="jumbo"></div> <div class="container"> <div class="content"></div> <div class="side"></div> </div> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> <script> $(window).resize(function() { var contentVar = $(".side"), navBar = $(".navbar"), startPosition = contentVar.offset().top, navBarPosition = navBar.offset().top + navBar.height(), contentWidth = contentVar.innerWidth(); var y = $(this).scrollTop(), currNavPos = y + navBar.height(); if(startPosition <= currNavPos) { contentVar.css("position", "fixed"); contentVar.css("top", 0); contentVar.css("right",0); contentVar.css("left", contentWidth); contentVar.css("transform", "translateY(200px)"); } else { contentVar.css("position", "relative"); contentVar.css("top", ""); contentVar.css("transform", ""); contentVar.css("right", ""); contentVar.css("left", ""); } }); var contentVar = $(".side"), navBar = $(".navbar"), startPosition = contentVar.offset().top, navBarPosition = navBar.offset().top + navBar.height(), contentWidth = $('.side').innerWidth(); $(document).scroll(function() { var y = $(this).scrollTop(), currNavPos = y + navBar.height(); if(startPosition <= currNavPos) { contentVar.css("position", "fixed"); contentVar.css("top", 0); contentVar.css("right",0); contentVar.css("left", contentWidth); contentVar.css("transform", "translateY(200px)"); } else { contentVar.css("position", "relative"); contentVar.css("top", ""); contentVar.css("transform", ""); contentVar.css("right", ""); contentVar.css("left", ""); } }); </script> </body> </html>
核心问题原因
现有代码的异常本质是全局缓存的尺寸、位置变量只在页面初始化时赋值一次,滚动事件触发时直接使用初始化时缓存的startPosition、contentWidth等旧值,窗口缩放后这些值已经和实际DOM尺寸不匹配,但代码没有重新计算更新,直接使用旧值设置定位样式就会导致布局错乱。另外resize事件中虽然重新计算了变量,但变量是事件回调内的局部变量,没有同步更新滚动事件使用的全局缓存值,滚动触发时依旧读取旧的全局变量,无法生效。
修复方法
把尺寸、位置计算逻辑抽成公共函数,在滚动、窗口缩放事件触发时都重新获取最新的DOM尺寸和位置值,不要缓存固定值即可解决问题,修复后的JS逻辑参考如下:
// 抽离公共计算与样式设置函数 function updateSidePosition() { var contentVar = $(".side"), navBar = $(".navbar"), // 每次触发都实时获取最新值,不做长期缓存 startPosition = contentVar.offset().top, navHeight = navBar.height(), contentWidth = contentVar.innerWidth(), y = $(window).scrollTop(), currNavPos = y + navHeight; if(startPosition <= currNavPos) { contentVar.css({ "position": "fixed", "top": navHeight, // 直接用导航栏高度做偏移,不需要写死固定值 "right": 0, "width": contentWidth, // 固定时显式设置宽度,避免flex布局下宽度塌陷 "left": "auto" }); } else { contentVar.removeAttr("style"); // 非固定状态直接清除内联样式,还原默认布局 } } // 页面初始化、滚动、窗口缩放时都触发计算 $(function() { updateSidePosition(); $(window).on("scroll resize", updateSidePosition); });
注意:固定side元素时不要用transform: translateY(200px)做偏移,直接把top值设为导航栏高度即可,避免导航栏高度变化时偏移量不匹配;同时固定时显式给side设置和原位置一致的宽度,避免fixed定位脱离flex布局后宽度自动撑开导致的尺寸异常。
内容的提问来源于stack exchange,提问作者SPM

