A: the preface

Recently, REACT was used to make a simulated Bilibili demo, which requires a scroll container and should have pull-up loading and pull-down refresh functions. I thought that BETTER Scroll was used in vue project before, so I will use it this time. Let’s put the final image first.

Two: Better scroll cannot scroll and solve the problem
  • The Content container contains elements that cannot be scrolled

    Verify that the content container is taller than the Wrapper

  • Content containers with images often don’t roll to the bottom

    The image may be loaded after the bs (Better-Scroll) instance is generated, resulting in errors in bs height calculation. The solution is that the img tag has a callback function (onLoad), which calls the refresh method of the BS instance. If there are many images, it is better to add a layer of anti-shake. Optimize performance

Three: experience sharing

Since we choose the Better-Scroll container, we will certainly encapsulate it so that we can use it easily. However, after we extract the Scroll and encapsulate it into a component, there will be a problem. If there is a picture wrapped in the children of the Scroll component, You can’t call the scroll refresh method after the image is loaded, so HERE I use eventBus to handle the scroll refresh operation after the image is loaded

Three: encapsulation and use
  • In the first place in the project installation better – scroll (website (core rolling | BetterScroll 2.0 (better – scroll. Making. IO)))

    yarn add better-scroll

  • Install the pull-up and pull-down components in the project’s index.js, where the core code is posted

    import Pulldown from '@better-scroll/pull-down';
    import Pullup from '@better-scroll/pull-up';
    import BScroll from "@better-scroll/core";
    
    BScroll.use(Pulldown)
    BScroll.use(Pullup)
    Copy the code
  • To install the eventBus

    yarn add events

    Encapsulate eventBus so it’s easy to use and maintain

    import {EventEmitter} from "events"; const event = new EventEmitter(); class EventUtils { static _instance = event; static emit(key, value = []) { this._instance.emit(key, ... value); } static addListener(key, callback) { this._instance.addListener(key, callback); } static removeListener(key, callback) { this._instance.removeListener(key, callback); } } class EventKey { static scrollRefresh(event = 'default') { return `${event}betterScrollRefresh`; } static scrollToTop(event = 'default') { return `${event}betterScrollToTop`; } } export { EventUtils, EventKey, }Copy the code
  • And then the Scroll component

    import {debounceUtils} from ".. /.. /.. /utils/function_utils"; import BScroll from "@better-scroll/core"; import {useEffect, useRef, useState} from "react"; import {EventUtils} from ".. /.. /.. /utils/event_utils"; import {PullDownProgress} from "./pull_down_progress"; import {BackTopButton} from "./back_top_button"; const pullUpDebounce = debounceUtils() const pullDownDebounce = debounceUtils() const scrollDebounce = debounceUtils() class ScrollDirection { static vertical = 'vertical'; static horizontal = 'horizontal'; } export function AppScroll(props) {// To initialize the better scroll const [controller, setController] = useState(null); const wrapperRef = useRef(); Const {refreshKey = 'default', toKey = 'default', children = (<div>scroll default </div>), scrollWidth = '100%', ScrollHeight = '100 px, scrollBackground =' rgba (229, 229, 229, 0.29) ', direction = ScrollDirection. Vertical, debounceDelay = 200, prototype = 1, click = true, showBackTop = false, showRefreshProgress = false, openPullDown = false, openPullUp = false, onRefresh = async () => {}, onLoadMore = async () => {}, } = props; const handlerPullDown = () => pullDownDebounce( async () => { if (controller === null) return; The console. The log (' drop-down '); await onRefresh(); controller.finishPullDown(); }, debounceDelay ); const handlerPullUp = () => pullUpDebounce( async () => { if (controller === null) return; On the console. The log (', '); await onLoadMore(); controller.finishPullUp(); }, debounceDelay ); const handlerRefresh = () => scrollDebounce( () => { if (controller === null) return; The console. The log (' refresh bs); controller.refresh(); }, debounceDelay ); const handlerBackTop = () => { if (controller === null) return; controller.scrollTo(0, 0, 100)} useEffect(() => {// Save the parent component or the newly generated better scroll instance = new BScroll(wrapperref.current, {scrollX: direction === ScrollDirection.horizontal, scrollY: direction === ScrollDirection.vertical, pullDownRefresh: openPullDown, pullUpLoad: openPullUp, prototype: prototype, click: click }); setController(instance); Return () => {console.log('AppScroll destruction '); instance.destroy(); setController(null); }}, []) useEffect(() => {if (controller === null) return; if (openPullDown) { controller.on('pullingDown', handlerPullDown); } if (openPullUp) { controller.on('pullingUp', handlerPullUp); } }, [handlerPullDown, HandlerPullUp]) useEffect(() => {// The parent passes eventBus to the Scroll component // refresh eventUtils.addListener (refreshKey, handlerRefresh); // Return the top event eventUtils.addListener (toKey, handlerBackTop); return () => { EventUtils.removeListener(refreshKey, handlerRefresh); EventUtils.removeListener(toKey, handlerBackTop); } }, [controller, refreshKey, toKey]) return ( <div ref={wrapperRef} style={{ width: scrollWidth, height: scrollHeight, background: scrollBackground, overflow: 'hidden' }} > <div className={"content position_relative"}> {showRefreshProgress && <PullDownProgress/>} {children} </div> {showBackTop && (<BackTopButton click={handlerBackTop}/>)} </div> ) }Copy the code
Four: the realization of the effect
  • Simple use of complete code

    import {AppScroll} from ".. /component/app_scroll"; function Profile() { return ( <AppScroll scrollHeight={'200px'}> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> <div>xxxxxxxxxxxxxx</div> </AppScroll> ) } export default ProfileCopy the code

  • Complex use of complete code

    import ".. /.. /.. /.. /assets/css/home.css" import {useCallback, useEffect, useState} from "react"; import VideoRow from ".. /.. /component/video_row"; import {homeInfoApi} from ".. /.. /.. /.. /network/api"; import {HomeDataModel} from ".. /.. /.. /.. /network/model"; import {AppScroll} from ".. /.. /component/app_scroll"; import {EventKey, EventUtils} from ".. /.. /.. /.. /utils/event_utils"; function HomeContent(props) { const {tag} = props; let [homeData, setHomeData] = useState(new HomeDataModel()); let [pageIndex, setPageIndex] = useState(1); const refreshKey = EventKey.scrollRefresh(tag.name); const toKey = EventKey.scrollToTop(tag.name); UseEffect (() => {// Return to the top of the page when switching a TAB, Eventutils.emit (toKey)}, [tag]) useEffect(() => {dataRefresh().then(); }, [tag]) async function dataRefresh() { try { const response = await homeInfoApi(tag.name); setPageIndex(1); setHomeData(response); } catch (e) { console.log(e); } } async function dataLoadMore() { try { const currentPage = pageIndex + 1; const response = await homeInfoApi(tag.name, currentPage); const currentHomeData = Object.assign({}, homeData); currentHomeData.videoList = homeData.videoList.concat(response.videoList); setPageIndex(currentPage); setHomeData(currentHomeData); } catch (e) { console.log(e); } } return ( <AppScroll refreshKey={refreshKey} toKey={toKey} scrollHeight={'calc(100vh - 56px * 3)'} scrollBackground={'rgba(229, 229, 229, 0.29)'} showRefreshProgress={true} showBackTop={true} openRefresh ={true} onRefresh={dataRefresh} OpenRefresh ={true} onLoadMore={dataLoadMore} > <div className={"home_content_container"}> { homeData.videoList.map((item, index) => <VideoRow { ... Object.assign( item, {imgLoaded: () => EventUtils.emit(refreshKey)} ) } key={item.vid + index} /> ) } </div> </AppScroll> ) } export default HomeContentCopy the code

  • Ok, this is the end, record my experience, also hope to help you in front of the screen, any questions are welcome to discuss in the comment section