utils
import BScroll from '@better-scroll/core' // BScroll 核心
import MouseWheel from '@better-scroll/mouse-wheel' // 引入滚轮
import PullDown from '@better-scroll/pull-down' // 上拉
import Scrollbar from '@better-scroll/scroll-bar'
BScroll.use(MouseWheel) // 下载滚轮插件
BScroll.use(PullDown) // 下载上拉
BScroll.use(Scrollbar)
const TIME_BOUNCE = 800 // 上拉弹跳时间
const REQUEST_TIME = 1000 // 请求处理时间
const THRESHOLD = 100 // 上拉高度执行
const STOP = 56 // 弹跳高度
let STEP = 0 // 页码
// Y轴滚动
export const BS = (wrapper, setBsContainer = false, action = false,) =>
{
const { payload } = action
if (wrapper)
{
if (typeof wrapper === 'string')
{
let wrapperRef = document.querySelector('#' + wrapper)
deploy(wrapperRef ? wrapperRef : null, setBsContainer ? setBsContainer : null, payload ? payload.callback : null)
} else
{
deploy(wrapper ? wrapper : null, setBsContainer ? setBsContainer : null, payload ? payload.callback : null)
}
}
}
// Y轴配置
const deploy = (wrapper, setBsContainer, callback) =>
{
let bs = new BScroll(wrapper, {
probeType: 3, // 默认0 不侦测 0和1 都不侦测 2:在手指滚动的过程中侦测 手指离开后的惯性滚动过程中不侦测 3:只要是滚动 都进行侦测
click: true, // 可以点击 用法:要覆盖本机滚动,BetterScroll必须禁止某些默认浏览器行为,例如鼠标单击。如果您希望您的应用程序响应click事件,则必须将该选项显式设置为true。然后BetterScroll将向其_constructed调度事件添加一个私有属性,该属性的值为true。
pullDownRefresh: true, // 向上拉 可以上拉 请求数据 加载更多
mouseWheel: true, // 鼠标滚轮
bounceTime: TIME_BOUNCE, // 上拉弹跳时间
pullDownRefresh: {
threshold: THRESHOLD,
stop: STOP
}
})
if (setBsContainer) setBsContainer(bs) // 用于保存 bs 对象
bs.on('scroll', position =>
{
if (callback)
{
const { scrolled } = callback
scrolled(position, bs) // 发送两个参数 position 位置 bs 对象
}
// 默认情况下 BScroll 是不可以实时监听滚动位置
// console.log(position); // 实时滚动的位置
})
bs.on('pullingDown', () =>
{
// 触发时机:在一次上拉加载的动作后 这个时机一般用来去后端请求数据
// bs.finishPullUp()
// 注意 只能回调一次
console.log('上拉加载更多');
// 先去发送网络请求 请求更多页的数据
// 等数据请求完成 并将数据展示出来
setTimeout(() =>
{
bs.finishPullDown() // 再次下拉
if (callback)
{
const PullBack = [TIME_BOUNCE, REQUEST_TIME, THRESHOLD, STOP, STEP]
const { pullUp } = callback
pullUp(PullBack, '上拉后发送的数据')
}
// bs.refresh()
// bs.finishPullUp() // 可以再次执行
}, 1500) // 两秒拉一次
})
bs.on('scrollEnd', e =>
{
console.log('滚动结束');
})
}
// X轴滚动 container 两个参数 一个 wrapper 一个滚动条的 ref setDsContainer 用来保存 DS action 暂时没写
export const DS = (container, setDsContainer = false, action = false) =>
{
const { wrapper, scrollBar } = container // wrapper 可以是 id scrollBar 必须是 Ref
const { payload } = action
console.log(wrapper, 'data');
if (wrapper)
{
if (typeof wrapper === 'string')
{
let wrapperRef = document.querySelector('#' + wrapper)
deployX(wrapper ? [wrapperRef, scrollBar] : null, setDsContainer ? setDsContainer : null, payload ? payload.callback : null)
} else
{
deployX(wrapper ? [wrapper, scrollBar] : null, setDsContainer ? setDsContainer : null, payload ? payload.callback : null)
}
}
}
// X轴配置
const deployX = (container, setDsContainer) => // horizontal 拿到自定义滚动条 ref
{
let [wrapper, horizontal] = container // 结构赋值
if (wrapper)
{
let ds = new BScroll(wrapper, {
probeType: 0, // 默认0 不侦测 0和1 都不侦测 2:在手指滚动的过程中侦测 手指离开后的惯性滚动过程中不侦测 3:只要是滚动 都进行侦测
click: true, // 可以点击 用法:要覆盖本机滚动,BetterScroll必须禁止某些默认浏览器行为,例如鼠标单击。如果您希望您的应用程序响应click事件,则必须将该选项显式设置为true。然后BetterScroll将向其_constructed调度事件添加一个私有属性,该属性的值为true。
mouseWheel: true, // 鼠标滚轮
scrollX: true,
scrollY: false,
scrollbar: {
customElements: [horizontal], // 自定义滚动条 [horizontal,vertical] 横竖 我只用了 horizontal 滚动条 DOM 传过来的 REF
fade: true, // 淡入淡出
interactive: true,
scrollbarTrackClickable: true
} // 滚动条
})
setDsContainer(ds) // 保存 ds
}
}
// 防抖函数
export const debounce = (func, delay) =>
{
let timer = null;
return function (...args)
{
if (timer) clearTimeout(timer);
timer = setTimeout(() =>
{
func.apply(this, args);
}, delay);
};
}
JS
import React, { useRef, useEffect, useState, Fragment } from 'react'
import styles from './style.less'
import { BS, DS, debounce } from './utils/index'
import { Select } from 'antd';
const { Option } = Select
const index = () =>
{
const [rollingArea, setRollingArea] = useState(0) // 滚动位置
const [bsContainer, setBsContainer] = useState(null) // 保存 BS
const [dsContainer, setDsContainer] = useState(null) // 保存 DS
const [beforePullDown, setBeforePullDown] = useState(true) // 正在加载中 ~
const [isPullingDown, setIsPullingDown] = useState(false) // 加载完成
const [selectName, setSelectName] = useState('yus')
const scrollRef = useRef() // X轴滚动
const scrollRefX = useRef() // Y轴滚动
const scrollBar = useRef() // 滚动条
useEffect(() =>
{ // 发送数据请求 设置订阅/启动定时器 手动更改 DOM 等 ~ // scrolled 记录滚动 pullUp 记录上拉
BS('wrapper', setBsContainer, { payload: { params: null, callback: { scrolled, pullUp } } }) // 传递三个参数 DOM节点、用于保存 BS 对象、 payload:传递一个 params 暂时没作用 callback 用于 接收传递过来的值
// BS(scrollRef.current) // 发送 Ref 也可以发送 DOM 节点 BS('wrapper')
DS({ wrapper: 'wrapperX', scrollBar: scrollBar.current }, setDsContainer)
// DS({ wrapper: scrollRefX.current, scrollBar: scrollBar.current }, setDsContainer)
return () =>
{ // 组件卸载之前 做一些收尾工作 比如清楚定时器/取消订阅 等 ~
}
}, []) // 检测数组内变量 如果为空 则监控全局
// 如果有数据请求 下拉再次请求
const requestData = () =>
{
console.log('请求数据了');
}
// 滚动事件
const scrolled = debounce((roll, bs) =>
{ // roll 滚动位置 bs BScroll 对象
const { x, y } = roll // x y 轴的距离
// bs && bs.refresh() // 按钮因为没有写到 content 也就不会影响到滚动的高度 所以也就不需要加 refresh
setRollingArea(y) // 保存 Y 轴位置
}, 100) // 展示的慢 因为加了防抖函数
const pullUp = async (data, news) =>
{
// console.log(news, '接收你发来的消息');
setBeforePullDown(false) // 更换上拉 顶部展示
setIsPullingDown(true)
// 如果有请求数据的话 页码加1 page += 1
await requestData() // 因为没有发送网络请求 就包了一个 定时器 有网络请求则不需要
setTimeout(() =>
{
setBeforePullDown(true)
setIsPullingDown(false)
}, 500)
}
// 点击到指定区域
const btnClick = debounce(() =>
{
console.log(dsContainer, 'dsContainer');
bsContainer.scrollTo(200, -1000, 2000) // X轴 Y轴 时间
dsContainer.scrollTo(-400, 0, 2000) // 控制 X 轴滚动
// dsContainer.scrollBy(400, 0, 2000) // 控制 X 轴滚动条
}, 1000)
// 返回顶部
const btnBack = () =>
{
bsContainer.scrollTo(750, 0, 2000) // X轴 Y轴 时间
dsContainer.scrollTo(0, -40, 2000) // 控制 X 轴滚动
}
// Dom list
const scrollList = () =>
{
return (
<Fragment>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<div>6</div>
<div>7</div>
<div>8</div>
<div>9</div>
<div>10</div>
<div>11</div>
<div>12</div>
<div>13</div>
<div>14</div>
<div>15</div>
<div>16</div>
<div>17</div>
<div>18</div>
<div>19</div>
<div>20</div>
<div>21</div>
<div>22</div>
<div>23</div>
<div>24</div>
<div>25</div>
<div>26</div>
<div>27</div>
<div>28</div>
<div>29</div>
<div>30</div>
<div>31</div>
<div>32</div>
<div>33</div>
<div>34</div>
<div>35</div>
<div>36</div>
<div>37</div>
<div>38</div>
<div>39</div>
<div>40</div>
<div>41</div>
<div>42</div>
<div>43</div>
<div>44</div>
<div>45</div>
<div>46</div>
<div>47</div>
<div>48</div>
<div>49</div>
<div>50</div>
<div>51</div>
<div>52</div>
<div>53</div>
<div>54</div>
<div>55</div>
<div>56</div>
<div>57</div>
<div>58</div>
<div>59</div>
<div>60</div>
<div>61</div>
<div>62</div>
<div>63</div>
<div>64</div>
<div>65</div>
<div>66</div>
<div>67</div>
<div>68</div>
<div>69</div>
<div>70</div>
<div>71</div>
<div>72</div>
<div>73</div>
<div>74</div>
<div>75</div>
<div>76</div>
<div>77</div>
<div>78</div>
<div>79</div>
<div>80</div>
<div>81</div>
<div>82</div>
<div>83</div>
<div>84</div>
<div>85</div>
<div>86</div>
<div>87</div>
<div>88</div>
<div>89</div>
<div>90</div>
<div>91</div>
<div>92</div>
<div>93</div>
<div>94</div>
<div>95</div>
<div>96</div>
<div>97</div>
<div>98</div>
<div>99</div>
<div>100</div>
</Fragment>
)
}
// Dom list
const scrollList2 = () =>
{
return (
<Fragment>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<div>6</div>
<div>7</div>
<div>8</div>
<div>9</div>
<div>10</div>
<div>11</div>
<div>12</div>
<div>13</div>
<div>14</div>
<div>15</div>
<div>16</div>
<div>17</div>
<div>18</div>
<div>19</div>
<div>20</div>
<div>21</div>
<div>22</div>
<div>23</div>
<div>24</div>
<div>25</div>
<div>26</div>
<div>27</div>
<div>28</div>
<div>29</div>
<div>30</div>
<div>31</div>
<div>32</div>
<div>33</div>
<div>34</div>
<div>35</div>
<div>36</div>
<div>37</div>
<div>38</div>
<div>39</div>
<div>40</div>
<div>41</div>
<div>42</div>
<div>43</div>
<div>44</div>
<div>45</div>
<div>46</div>
<div>47</div>
<div>48</div>
<div>49</div>
<div>50</div>
<div>51</div>
<div>52</div>
<div>53</div>
<div>54</div>
<div>55</div>
<div>56</div>
<div>57</div>
<div>58</div>
<div>59</div>
<div>60</div>
<div>61</div>
<div>62</div>
<div>63</div>
<div>64</div>
<div>65</div>
<div>66</div>
<div>67</div>
<div>68</div>
<div>69</div>
<div>70</div>
<div>71</div>
<div>72</div>
<div>73</div>
<div>74</div>
<div>75</div>
<div>76</div>
<div>77</div>
<div>78</div>
<div>79</div>
<div>80</div>
<div>81</div>
<div>82</div>
<div>83</div>
<div>84</div>
<div>85</div>
<div>86</div>
<div>87</div>
<div>88</div>
<div>89</div>
<div>90</div>
<div>91</div>
<div>92</div>
<div>93</div>
<div>94</div>
<div>95</div>
<div>96</div>
<div>97</div>
<div>98</div>
<div>99</div>
<div>100</div>
<div>101</div>
<div>102</div>
<div>103</div>
<div>104</div>
<div>105</div>
<div>106</div>
<div>107</div>
<div>108</div>
<div>109</div>
<div>110</div>
<div>111</div>
<div>112</div>
<div>113</div>
<div>114</div>
<div>115</div>
<div>116</div>
<div>117</div>
<div>118</div>
<div>119</div>
<div>120</div>
<div>121</div>
<div>122</div>
<div>123</div>
<div>124</div>
<div>125</div>
<div>126</div>
<div>127</div>
<div>128</div>
<div>129</div>
<div>130</div>
<div>131</div>
<div>132</div>
<div>133</div>
<div>134</div>
<div>135</div>
<div>136</div>
<div>137</div>
<div>138</div>
<div>139</div>
<div>140</div>
<div>141</div>
<div>142</div>
<div>143</div>
<div>144</div>
<div>145</div>
<div>146</div>
<div>147</div>
<div>148</div>
<div>149</div>
<div>150</div>
</Fragment>
)
}
const listX = () =>
{
return (
<Fragment>
<span>羽神天下第一</span>
<span>2</span>
<span>3</span>
<span>4</span>
<span>5</span>
<span>6</span>
<span>7</span>
</Fragment>
)
}
const listX2 = () =>
{
return (
<Fragment>
<span>少主天下第一</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>10</span><span>11</span><span>12</span><span>13</span><span>14</span><span>15</span><span>16</span><span>17</span><span>18</span><span>19</span><span>20</span>
</Fragment>
)
}
// 下拉框
const handleChange = async (value) => // 点击下拉框改变了容器高度 则必须要 refresh 刷新容器
{
await bsContainer.scrollTo(0, 0, 400) // 会到顶部
await dsContainer.scrollTo(0, 0, 200)
setSelectName(value)
await bsContainer.refresh()
await dsContainer.refresh()
}
return (
<div className={styles.container}>
{rollingArea <= -1000 ? (<button onClick={btnBack} className={styles.btn} >点击返回顶部</button>) : null}
<Select defaultValue={selectName} style={{ width: 120 }} onChange={handleChange}>
<Option value="yus">羽神</Option>
<Option value="sz">少主</Option>
</Select>
<div id='wrapper' ref={scrollRef} className={styles.wrapper}>
<div className={styles.content}>
{beforePullDown ? <div className={styles.status}>正在加载中~</div> : null}
{isPullingDown ? <div className={styles.status}>数据加载成功了哦</div> : null}
<button onClick={btnClick}>按钮点击</button>
{selectName === 'yus' ? scrollList() : null}
{selectName === 'sz' ? scrollList2() : null}
</div>
</div>
<div ref={scrollRefX} id="wrapperX" className={styles.scrollCoreX}>
<div className={styles.scrollCoreYContent}>
{selectName === 'yus' ? listX() : null}
{selectName === 'sz' ? listX2() : null}
</div>
{/* 自定义滚动条 */}
<div ref={scrollBar} className={styles.horizontal_scrollbar}>
<div className={styles.horizontal_indicator}></div>
</div>
</div>
</div >
)
}
export default index
CSS
.container {
position: relative;
width: 100%;
height: 100%;
.wrapper {
position: relative;
margin: 0 auto;
width: 800px;
height: 400px;
background-color: pink;
overflow: hidden;
}
}
.status {
position: absolute;
width: 100%;
padding: 20px;
box-sizing: border-box;
transform: translateY(-100%) translateZ(0);
text-align: center;
color: #999;
}
.btn {
position: fixed;
top: 100px;
right: 600px;
z-index: 1;
}
// BetterScroll 实现了水平滚动,对 CSS 要求更高。首先,您需要确保包装器不包装,并且内容的显示是内联块。
/*
.scroll-wrapper
// ...
white-space nowrap
.scroll-content
// ...
display inline-block
*/
.scrollCoreX {
white-space: nowrap;
margin: 30px auto;
width: 600px;
height: 200px;
background-color: yellow;
overflow: hidden;
.scrollCoreYContent {
display: inline-block;
span {
display: inline-block;
margin: 20px;
width: 200px;
height: 200px;
background-color: burlywood;
}
}
.horizontal_scrollbar {
position: absolute;
left: 50%;
bottom: 10px;
width: 100px;
height: 7px;
border-radius: 5px;
transform: translateX(-50%) translateZ(0);
background-color: rgb(200, 200, 200, 0.3);
.horizontal_indicator {
height: 100%;
width: 20px;
border-radius: 5px;
background-color: #db8090;
}
}
}