The introduction
In the past, I worked on a small project about the development of H5 on mobile terminal using React + TSX. However, I didn’t know why it was necessary to adapt to the desktop terminal suddenly. In order to get a relatively consistent experience on both the mobile terminal and the desktop terminal, I used Better-Scroll.
Among them, I stepped on a lot of pits using Better-Scroll, now I will record some experience and configuration using Better-Scroll.
Why can’t I scroll
First, you need to understand why scrolling works. It’s simple: the height of the parent container is smaller than the height of the child element.
Before this, we first look at the browser scroll principle: browser scroll bar we will encounter, when the height of the page content exceeds the height of the viewport, there will be vertical scroll bar; A horizontal scroll bar appears when the width of the page content exceeds the viewport width. That is, when our viewport can not display the content, the user will scroll through the screen to see the rest of the content.
Second, the element you need to scroll through should be the first child of the parent element.
Note that BetterScroll handles scrolling of the first child element of the Wrapper by default and all other elements are ignored.
Third, why do I satisfy the above two, why can’t I scroll? If your content uses asynchronous data, the better scroll height will be the same as the original height, which of course will not be able to scroll. The solution is to refresh the asynchronous data after getting it. Or use the @better-scroll/observe-dom plugin to automatically update the height.
Configuration and initialization
Here I have used several plug-ins provided by Better Scroll, ObserveDOM, MouseWheel, ScrollBar, PullDown and Pullup.
General structure
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
Copy the code
Ok, we’re done, now we’re ready to instantiate better Scroll
BetterScroll provides a class that instantiates a native DOM object as the first argument. Of course, if a string is passed, BetterScroll internally tries to call querySelector to retrieve the DOM object.
// The outer wrap instance
const wrapRef = useRef<HTMLDivElement>(null)
// Record whether the Better scroll is instantiated to prepare for subsequent mount drop-down refresh and drop-down load
const initRef = useRef(false)
// Store an instance of better-Scroll
const [scrollObj, setscrollObj] = useState<BScrollConstructor>()
// Better Scroll configuration parameters
const initBScroll = () = > {
setscrollObj(
new BScroll(wrapRef.current as HTMLDivElement, {
//probeType is 3, which can send scroll events at any time, including scrollTo or momentum scrolling animation
probetype: 3.// Native click can be used
click: true.// Detect DOM changes
observeDOM: true.// Mouse wheel Settings
mouseWheel: {
speed: 20.invert: false.easeTime: 300
},
// Displays a scroll bar
scrollY: true.scrollbar: true.// Overanimate, the scroll bar will have an overanimate when downloading more
useTransition: true.// Drop refresh
pullDownRefresh: {
threshold: 70.stop: 0
},
// Pull up to load more
pullUpLoad: {
threshold: 90.stop: 10}}}))Copy the code
Then, during the component mount phase, we instantiate the Better-Scroll and add drop and pull listeners to it
// The object is initialized
useEffect(() = > {
initBScroll()
return () = > {
// Remember to destroy the component when uninstalling itscrollObj? .destroy() } }, [])// Drop refresh
const pulldown = async () => {
onPulldown && (await onPulldown())
setTimeout(() = > {
// Remember to use finishPullDown, otherwise you can only pull down oncescrollObj? .finishPullDown()// Your content changes after the drop down, and if you don't use refresh, you need to swipe to refresh the height of the contentscrollObj? .refresh() },500)}// Pull up load
const pullup = async () => {
onPullup && (await onPullup())
setTimeout(() = >{ scrollObj? .finishPullUp() scrollObj? .refresh() },500)}// Object event mount
useEffect(() = > {
if (initRef.current === true) {
// Drop refresh
// Each update needs to remove the previous pullingDown event, otherwise it will accumulatescrollObj? .off("pullingDown"); scrollObj? .once("pullingDown", pulldown);
// Pull up load
// Each update needs to remove the previous pullingUp event, otherwise it will accumulatescrollObj? .off("pullingUp"); scrollObj? .once("pullingUp", pullup);
} else {
initRef.current = true;
}
// Why do I listen on prop because I can't listen on external state changes
// handlePullUp's [...state,...res.data] state will always be the original []
}, [prop]);
Copy the code
practice
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[]>([]);
// Start by pulling data
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;
Copy the code
The effect
conclusion
Better scroll is a very good tool, unfortunately I am not very familiar with react, I wasted a lot of time on the useEffect dependency parameter adjustment to solve the problem of pulling up and down multiple times and the parent component’s state is always empty. If you have a better solution, please leave a comment in the comments section, we will make progress together.
Refer to the link
2.0 guide BetterScroll
2.0 plug-in BetterScroll
When Better-Scroll meets Vue
I want to make a React version of the demo