使用 vw 产生的问题
当前区块宽度是 1500px,为居中布局,想要给这个区块加一张占满整个屏幕宽度的背景图。说明:该页面是框架通过 mdx 文件生成的,故无法将 banner 元素宽度直设置为整屏
一般来说,我们可以让img绝对定位即可实现效果,代码如下。
<div class="parent">
<img class="img-bg" />
<div>...</div>
</div>
.parent {
position: relative;
}
.img-bg {
position: absolute;
left: calc(50% - 50vw);
top: 0;
width: 100vw;
height: 100%;
}
然而,一旦当前页面出现了竖向滚动条,你会发现同时会多一个横向滚动条,这是怎么来的呢?
vw
在规范中我们可以看到这段描述,也就是说滚动条是不会影响 vw 单位计算的,其始终是基于视口宽度计算的,有无滚动条 vw 宽度一致,然而内部元素是会被滚动条压缩空间的!也就是设置img宽度为100vw时是超过内容宽度的,这也就是出现水平滚动条的原因。
The viewport-percentage lengths are relative to the size of the initial containing block. When the height or width of the initial containing block is changed, they are scaled accordingly. However, any scrollbars are assumed not to exist.
问题的解决
- 首先当前页面是否有滚动条是不确定的,不论是否有滚动条都需要兼容展示,但是滚动条会影响 vw 单位,我们在计算宽度和 left 时是需要将滚动条考虑进去的
- 也就是说,我们需要知道当前页面是否具有滚动条,再针对两种场景做处理。一般情况下,使用
document.body.scrollHeight > window.innerHeight就可以判断。但是在 IE7,IE8 中window.innerHeight为underfined,所以为了兼容 IE7、IE8,需要使用document.documentElement.clientHeight属性计算窗口高度。
const hasScrollbar = () => {
return (
document.body.scrollHeight >
(window.innerHeight || document.documentElement.clientHeight)
);
};
- 因为不同浏览器滚动条宽度是不同的(比如在 macOS 上滚动条其实是悬浮在页面上的,并不占据空间),所以我们还需要计算出滚动条的宽度,可以创建临时元素来计算:
const getScrollbarWidth = () => {
const container = document.createElement("div");
document.body.appendChild(container);
container.style.overflow = "scroll";
const inner = document.createElement("div");
container.appendChild(inner);
const width = container.offsetWidth - inner.offsetWidth;
document.body.removeChild(container);
return width;
};
- 我们可以在页面初始化时执行一下这两个函数,并存储在
state中,元素样式根据滚动条情况做兼容即可。延时 500ms 是为了让页面元素尽量加载完成。
const [isHaveScrollbar, setIsHaveScrollbar] = useState(false);
const [scrollbarWidth, setScrollbarWidth] = useState(15);
React.useEffect(() => {
setTimeout(() => {
setIsHaveScrollbar(hasScrollbar());
setScrollbarWidth(getScrollbarWidth());
}, 500);
}, []);
<div
style={{
width: isHaveScrollbar
? `calc(100vw - ${scrollbarWidth}px)`
: "100vw",
left: isHaveScrollbar
? `calc(50% - 50vw + ${scrollbarWidth / 2}px)`
: "calc(50% - 50vw)",
}}
>...</div>
总结
至此,我们兼容了不同系统,不同浏览器,是否有滚动条的情况,使用 vw 不会再导致布局问题了。以上是一个粗略的解决方案,不过对于我当前的应用场景已经足够了。
- 其实我们可以在计算滚动条宽度后将其设置为 css 变量,只需要在 layout 中执行一次即可,方便各处使用。
- 其次,延时 500ms 执行在某些特定场景下并不靠谱,比如页面中有未知高度的图片元素,且在 500ms 内未加载完成,那么会导致是否有滚动条的判断结果不正确,这种情况下我们可以监听图片的加载,全部图片加载完成后再次执行判断函数,以保证状态正确。