首先,你要知道为什么可以滚动,原理很简单,父容器的高度比子元素的小即可。
在这之前,我们先来看一下浏览器的滚动原理: 浏览器的滚动条大家都会遇到,当页面内容的高度超过视口高度的时候,会出现纵向滚动条;当页面内容的宽度超过视口宽度的时候,会出现横向滚动条。也就是当我们的视口展示不下内容的时候,会通过滚动条的方式让用户滚动屏幕看到剩余的内容。
第二,你需要滚动的元素应该是父元素下的第一个子元素。
这里要注意的是,BetterScroll 默认处理容器(wrapper)的第一个子元素(content)的滚动,其它的元素都会被忽略。
第三,为什么我满足了上面两条,为什么还是不能滚动?可能你的content用了异步的数据,better-scroll实例化之后content的高度还是初始时的高度,这当然无法滚动,解决方法是获取到了异步的数据之后使用refresh()
更新,或是使用插件@better-scroll/observe-dom
来自动更新高度, 或者observeDOM: true,
。
配置与初始化
这里我使用了better-scroll
官方提供的几个插件,ObserveDOM
、MouseWheel
、ScrollBar
、PullDown
和Pullup
。
大致的结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | import BScroll from '@better-scroll/core' import { BScrollConstructor } from '@better-scroll/core/dist/types/BScroll' import ObserveDOM from '@better-scroll/observe-dom' import MouseWheel from '@better-scroll/mouse-wheel' import ScrollBar from '@better-scroll/scroll-bar' import PullDown from '@better-scroll/pull-down' import Pullup from '@better-scroll/pull-up' export interface ScrollProps { wrapHeight: string ; prop?: any; onPullup?: Function; onPulldown?: Function; } const Scroll: React.FC<ScrollProps> = ({ wrapHeight, prop, onPullup, onPulldown, children,}) => { BScroll.use(ObserveDOM) BScroll.use(MouseWheel) BScroll.use(ScrollBar) BScroll.use(PullDown) BScroll.use(Pullup) // ... return ( <div className= "scroll-warpper" ref ={wrapRef} style={{ height: wrapHeight, overflow: 'hidden' }}> <div className= "scroll-content" > {children} </div> </div> ) } export default Scroll |
ok,准备工作完成,接下来准备better-scroll
的实例化
BetterScroll 提供了一个类,实例化的第一个参数是一个原生的 DOM 对象。当然,如果传递的是一个字符串,BetterScroll 内部会尝试调用 querySelector 去获取这个 DOM 对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | // 外层的wrap实例 const wrapRef = useRef<HTMLDivElement>( null ) // 记录Better-scroll是否实例化,为后续挂载下拉刷新和上拉加载做准备 const initRef = useRef( false ) // 存储better-scroll的实例 const [scrollObj, setscrollObj] = useState<BScrollConstructor>() // better-scroll的配置参数 const initBScroll = () => { setscrollObj( new BScroll(wrapRef.current as HTMLDivElement, { //probeType 为 3,任何时候都派发 scroll 事件,包括调用 scrollTo 或者触发 momentum 滚动动画 probetype: 3, // 可以使用原生的点击 click: true , // 检测dom变化 observeDOM: true , // 鼠标滚轮设置 mouseWheel: { speed: 20, invert: false , easeTime: 300 }, // 显示滚动条 scrollY: true , scrollbar: true , // 过度动画, 在下载更多的时候滚动条会有个过度动画 useTransition: true , // 下拉刷新 pullDownRefresh: { threshold: 70, stop: 0 }, // 上拉加载更多 pullUpLoad: { threshold: 90, stop: 10 } }) ) } |
接着是在组件挂载阶段时,将better-scroll进行实例化,以及为其添加下拉和上拉监听函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | // 对象初始化 useEffect(() => { initBScroll() return () => { // 组件卸载时记得将其销毁 scrollObj?.destroy() } }, []) // 下拉刷新 const pulldown = async () => { onPulldown && (await onPulldown()) setTimeout(() => { // 记得使用finishPullDown,不然你只能下拉一次 scrollObj?.finishPullDown() // 下拉之后你的content会发生变化,如果不使用refresh,你需要滑动一下才能刷新content的高度 scrollObj?.refresh() }, 500) } // 上拉加载 const pullup = async () => { onPullup && (await onPullup()) setTimeout(() => { scrollObj?.finishPullUp() scrollObj?.refresh() }, 500) } // 对象事件挂载 useEffect(() => { if (initRef.current === true ) { // 下拉刷新 // 每次更新都需要先把之前的pullingDown事件清除,不然会累加 scrollObj?.off( "pullingDown" ); scrollObj?.once( "pullingDown" , pulldown); // 上拉加载 // 每次更新都需要先把之前的pullingUp事件清除,不然会累加 scrollObj?.off( "pullingUp" ); scrollObj?.once( "pullingUp" , pullup); } else { initRef.current = true ; } // 为什么监听prop是因为这边监听不到外面的state变化 // handlePullUp的[...state, ...res.data]中的state会中始终为一开始的[] }, [prop]); |
实践
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | import React, { CSSProperties, useEffect, useState, useCallback } from "react" ; import Scroll from "./scroll" ; import axios, { Method } from "axios" ; export interface TestProps {} interface ResponseType { code: number; data: any; } const Test: React.FC<TestProps> = () => { const style: CSSProperties = { width: "500px" , }; const request = (url: string , method: Method): Promise<ResponseType> => { return new Promise((resolve, reject) => { const options = { url, method, }; axios(options) .then((res) => { const data = res.data as ResponseType; resolve(data); }) . catch ((err) => reject(err)); }); }; const getData = () => request( "/api/datasource" , "GET" ); const getMore = () => request( "/api/abc" , "GET" ); const [state, setstate] = useState<any[]>([]); // 一开始拉取数据 useEffect(() => { (async function () { const res = await getData(); console.log(res); res.code === 0 && setstate(res.data); })(); }, []); const handlePullUp = useCallback(async () => { const res = await getMore(); res.code === 0 && setstate(state.concat(res.data)); }, [state]); async function handlePullDown() { const res = await getData(); res.code === 0 && setstate(res.data); } return ( <div style={style}> <Scroll wrapHeight= "300px" prop={state} onPullup={handlePullUp} onPulldown={handlePullDown} > {state.map((item, idx) => idx % 2 === 0 ? ( <div key={idx} style={{ height: "200px" , background: "red" }}> {item} </div> ) : ( <div key={idx} style={{ height: "200px" , background: "green" }}> {item} </div> ) )} </Scroll> </div> ); }; export default Test; |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
2020-09-09 pip报failed to create process