平滑滚动:scrollIntoView、react-scroll
禁止滚动:桌面端禁止滚动,移动端禁止滚动
监听滚动:IntersectionObserver
参考课程:Build Responsive Real-World Websites with HTML and CSS
视频 | 源码
基于React的响应式单页应用开发、优化和部署
平滑滚动
实现平滑且可被浏览器兼容的滚动
使用场景:
点击导航栏连接,产生一个动画平滑地滚动到指定位置
一般方式
导航栏中的按钮为a标签实现的超链接
1 | <a href="#cta" class="btn btn--full margin-right-sm">Start eating well</a> |
js脚本中选中所有a: link
标签,手动绑定点击事件
- 阻止默认行为
- 获取每个a标签的href属性,得到转跳目标元素的id
- 获取目标元素,调用
scrollIntoView
方法,设置参数behavior: "smooth"
,即可平滑滚动
但是该方法不适用于React的设计逻辑
1 | const allLinks = document.querySelectorAll("a:link"); |
其他方式参考:实现页面平滑滚动的N种姿势
React-scroll
使用React插件react-scroll
使用例:https://codesandbox.io/s/basic-6t84k
常用API
1 | import { Link, animateScroll as scroll} from 'react-scroll' |
Link
参数 | 用例 | 含义 |
---|---|---|
activeClass | ‘active’ | 判断滚动到指定的元素后,会在Link元素上加上一个css class,一般设类名为active |
to | ‘cta’ | 目标元素id或name |
spy | true | 判断滚动到指定的元素后,使对应的Link元素变成选中状态(就是加上activeClass) |
hashSpy | true | 判断滚动到指定的元素后,在网页url后面显示哈希路径,例如 localhost:3000/#meals |
smooth | true | 是否平滑 |
offset | -80 | 额外滚动距离,单位为px,传参时直接传数值 |
duration | 500 | 持续时间,单位为ms |
animateScroll as scroll
options
1 | { |
Scroll To Top
1 | scroll.scrollToTop(options); |
Scroll To Bottom
1 | scroll.scrollToBottom(options); |
Scroll To (position)
1 | scroll.scrollTo(100, options); |
Scroll To (Element)
1 | scroll.scrollTo(element, options); |
Scroll More (px)
1 | scroll.scrollMore(10, options); |
手写封装:NavLink
入参:
- className:给a标签加上class
- to:目标元素id
- onClick:除了转跳意外的点击事件处理函数
- props.children
1 | import { Link, animateScroll as scroll } from 'react-scroll' |
使用:
1 | <NavLink className='main-nav-link' to="how" onClick={onCloseHandle}>How it works</NavLink> |
问题
spy和hashSpy会出现滚动到指定位置,但是判定激活的是上面的当前元素上面的那个元素,目前没有找到解决方案
参考:https://github.com/fisshy/react-scroll/issues/422
I solved this by letting react-waypoint do the “set active” handling instead. So I removed
spy
andonSetActive
from my<Link>
elements and wrapped each targeted element with<Waypoint>
. That way you get more control of when the target element is activated, and you can offset the triggers freely with using waypoints, while still keeping any scroll offset set in the<Link>
element.
阻止滚动
场景:
产生弹窗或打开导航栏时,不希望下层的页面能够滚动,需要手动禁用
桌面端
粗暴的方法:
html或body元素的overflow设为hidden(测试后是加在html元素上有效)
1 | document.documentElement.style.overflow = 'hidden' |
问题:
这样设置会影响子元素的overflow属性,造成页面布局抖动,不好
其他解决方案:
参考:阻止滚轮滚动事件
监听鼠标滚轮滚动mousewheel事件,阻止默认行为
问题:
用户仍然可以拖动滚动条进行操作
1 | const preventScroll = useCallback((e) => { |
可以获取滚动条宽度,设置html元素 overflow: hidden 的同时,给它一个 margin-right,这样就可以避免抖动
移动端
考虑兼容性的方法:touchmove事件,阻止默认行为
原理:参考 React禁止页面滚动踩坑实践与方案梳理
1 | const preventScroll = useCallback((e) => { |
监听滚动
场景:监听页面滚动,如果滚动到指定位置触发事件
IntersectionObserver
文档:https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver
IntersectionObserver
接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。祖先元素与视窗(viewport)被称为根(root)。
可以用于检测某一元素是否滚动到窗口内,或者在窗口类滚动到的比例
API
使用new
关键字调用 IntersectionObserver
接口,返回一个新的IntersectionObserver
对象,当其监听到目标元素的可见部分穿过了一个或多个**阈(thresholds)**时,会执行指定的回调函数。
1 | var observer = new IntersectionObserver(callback[, options]); |
入参:
回调函数,当元素可见比例超过指定阈值后,会调用该回调函数,此回调函数接受两个参数:
entries
一个
IntersectionObserverEntry
对象的数组,每个被触发的阈值,都或多或少与指定阈值有偏差。observer
被调用的
IntersectionObserver
实例。一般只传entries
options
可选一个可以用来配置observer实例的对象。如果
options
未指定,observer实例默认使用文档视口作为root,并且没有margin,阈值为0%(意味着即使一像素的改变都会触发回调函数)。可以指定以下配置:root
监听元素的祖先元素
Element
对象,其边界盒将被视作视口。目标在根的可见区域的的任何不可见部分都会被视为不可见。rootMargin
一个在计算交叉值时添加至根的边界盒(bounding_box (en-US))中的一组偏移量,类型为字符串(string) ,可以有效的缩小或扩大根的判定范围从而满足计算需要。语法大致和CSS 中的
margin
属性等同; 可以参考 The root element and root margin in Intersection Observer API来深入了解margin的工作原理及其语法。默认值是”0px 0px 0px 0px”。threshold
规定了一个监听目标与边界盒交叉区域的比例值,可以是一个具体的数值或是一组0.0到1.0之间的数组。若指定值为0.0,则意味着监听元素即使与根有1像素交叉,此元素也会被视为可见. 若指定值为1.0,则意味着整个元素都在可见范围内时才算可见。可以参考Thresholds in Intersection Observer API 来深入了解阈值是如何使用的。阈值的默认值为0.0。
实例
以下使用场景:
- 检测某一元素(section-hero)是否滚动到视窗内,滚动则在body元素上绑定新css属性(’sticky’)
1 | const Hero = () => { |