index.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. <template>
  2. <view :class="['s-pull-scroll',customClass]" @touchmove.stop.prevent="e=>e.preventDefault()">
  3. <scroll-view
  4. :id="scrollId"
  5. class="s-pull-scroll-view"
  6. :class="{'is-fixed':fixed}"
  7. :style="{'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}"
  8. :scroll-top="scrollTop"
  9. :scroll-with-animation="false"
  10. :scroll-y="scrollAble"
  11. :enable-back-to-top="true"
  12. @scroll="scroll"
  13. @touchstart="touchstart"
  14. @touchmove="touchmove"
  15. @touchend="touchend"
  16. @touchcancel="touchend"
  17. >
  18. <view :style="{'transform': translateY, 'transition': transition}">
  19. <view class="s-pull-down-wrap" :style="{'height':downOffset+'rpx'}">
  20. <view
  21. class="s-pull-loading-icon"
  22. :class="{'s-pull-loading-rotate':isDownLoading}"
  23. :style="{'transform':downRotate}"
  24. ></view>
  25. <view>{{downText}}</view>
  26. </view>
  27. <slot></slot>
  28. <view v-if="isUpLoading" class="s-pull-up-wrap">
  29. <view class="s-pull-loading-icon s-pull-loading-rotate"></view>
  30. <view>{{loadingText}}</view>
  31. </view>
  32. <slot v-if="isEmpty" name="empty">
  33. <view class="s-pull-tip-wrap" v-if="emptyText">{{emptyText}}</view>
  34. </slot>
  35. <slot v-else-if="isError" name="error">
  36. <view class="s-pull-tip-wrap" v-if="errorText" @click="onErrorClick">{{errorText}}</view>
  37. </slot>
  38. <slot v-else-if="isFinish" name="finish">
  39. <view class="s-pull-tip-wrap" v-if="finishText && isShowFinishText">{{finishText}}</view>
  40. </slot>
  41. </view>
  42. </scroll-view>
  43. <!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
  44. <view v-if="backTop" :class="['s-pull-back-top',{'show':isShowBackTop}]" @click="onBackTop">
  45. <slot name="backtop">
  46. <view class="default-back-top">
  47. <img src="./back-top.png" />
  48. </view>
  49. </slot>
  50. </view>
  51. </view>
  52. </template>
  53. <script>
  54. export default {
  55. name: 's-pull-scroll',
  56. data () {
  57. Object.assign(this, {
  58. pullType: '',
  59. scrollRealTop: 0, // 滚动条的位置
  60. preScrollY: 0,
  61. clientNum: 0,
  62. isExec: false,
  63. scrollHeight: 0,
  64. clientHeight: 0,
  65. bodyHeight: 0,
  66. windowTop: 0, // 可使用窗口的顶部位置
  67. windowBottom: 0, // 可使用窗口的底部位置
  68. page: 0,
  69. startPoint: null,
  70. lastPoint: null,
  71. startTop: 0,
  72. maxTouchmoveY: 0,
  73. inTouchend: false,
  74. moveTime: 0,
  75. moveTimeDiff: 0,
  76. movetype: 0,
  77. isMoveDown: false
  78. });
  79. return {
  80. scrollId: 's-pull-scroll-view-id-' + Math.random().toString(36).substr(2), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
  81. downHight: 0, // 下拉刷新: 容器高度
  82. downRotate: 0, // 下拉刷新: 圆形进度条旋转的角度
  83. downText: '', // 下拉刷新: 提示的文本
  84. isDownReset: false, // 下拉刷新: 是否显示重置的过渡动画
  85. isDownLoading: false, // 下拉刷新: 是否显示加载中
  86. isUpLoading: false, // 上拉加载: 是否显示 "加载中..."
  87. isFinish: false, // 是否加载完毕
  88. isEmpty: false, // 是否显示空布局
  89. isError: false, // 是否加载出错
  90. isShowBackTop: false, // 是否显示回到顶部按钮
  91. scrollAble: true, // 是否禁止下滑 (下拉时禁止,避免抖动)
  92. scrollTop: 0, // 滚动条的位置
  93. isShowFinishText: false
  94. };
  95. },
  96. props: {
  97. // class
  98. customClass: {
  99. type: String,
  100. default: ''
  101. },
  102. // 是否通过fixed固定高度, 默认true
  103. fixed: {
  104. type: Boolean,
  105. default: true
  106. },
  107. // 距顶部(rpx)
  108. top: {
  109. type: [Number, Array, String],
  110. default () {
  111. return 0;
  112. }
  113. },
  114. // 距底部(rpx)
  115. bottom: {
  116. type: [Number, Array, String],
  117. default () {
  118. return 0;
  119. }
  120. },
  121. // 提示文案
  122. loadingText: {
  123. type: String,
  124. default: '加载中 ...'
  125. },
  126. pullingText: {
  127. type: String,
  128. default: '下拉刷新'
  129. },
  130. loosingText: {
  131. type: String,
  132. default: '释放刷新'
  133. },
  134. finishText: {
  135. type: String,
  136. default: '暂无更多了'
  137. },
  138. emptyText: {
  139. type: String,
  140. default: '暂无数据'
  141. },
  142. errorText: {
  143. type: String,
  144. default: '加载失败,点击重新加载'
  145. },
  146. // 下拉配置
  147. // 下拉回掉,参数为vm
  148. pullDown: Function,
  149. downOffset: {
  150. type: Number,
  151. default: 100
  152. },
  153. downFps: {
  154. type: Number,
  155. default: 40
  156. },
  157. downMinAngle: {
  158. type: Number,
  159. default: 45
  160. },
  161. downInOffsetRate: {
  162. type: Number,
  163. default: 1
  164. },
  165. downOutOffsetRate: {
  166. type: Number,
  167. default: 0.4
  168. },
  169. downStartTop: {
  170. type: Number,
  171. default: 100
  172. },
  173. downBottomOffset: {
  174. type: Number,
  175. default: 20
  176. },
  177. // 上拉配置
  178. // 上拉回掉,参数为vm
  179. pullUp: Function,
  180. upOffset: {
  181. type: Number,
  182. default: 160
  183. },
  184. // 回到顶部
  185. backTop: Boolean,
  186. backTopOffset: {
  187. type: Number,
  188. default: 1000
  189. }
  190. },
  191. watch: {
  192. top () {
  193. this.refreshClientHeight();
  194. },
  195. bottom () {
  196. this.refreshClientHeight();
  197. }
  198. },
  199. computed: {
  200. // top数值,单位rpx,需转成px. 目的是使下拉布局往下偏移
  201. numTop () {
  202. return (Array.isArray(this.top) ? this.top : [this.top]).map(num => uni.upx2px(Number(num || 0))).reduce((a, b) => a + b) || 0;
  203. },
  204. // bottom数值,单位rpx,需转成px 目的是使上拉布局往上偏移
  205. numBottom () {
  206. return (Array.isArray(this.bottom) ? this.bottom : [this.bottom]).map(num => uni.upx2px(Number(num || 0))).reduce((a, b) => a + b) || 0;
  207. },
  208. numBackTopOffset () {
  209. return uni.upx2px(Number(this.backTopOffset || 0));
  210. },
  211. numDownBottomOffset () {
  212. return uni.upx2px(Number(this.downBottomOffset || 0));
  213. },
  214. numDownStartTop () {
  215. return uni.upx2px(Number(this.downStartTop || 0));
  216. },
  217. numDownOffset () {
  218. return uni.upx2px(Number(this.downOffset || 0));
  219. },
  220. numUpOffset () {
  221. return uni.upx2px(Number(this.upOffset || 0));
  222. },
  223. fixedTop () {
  224. return this.fixed ? (this.numTop + this.windowTop) + 'px' : 0;
  225. },
  226. padTop () {
  227. return !this.fixed ? this.numTop + 'px' : 0;
  228. },
  229. fixedBottom () {
  230. return this.fixed ? (this.numBottom + this.windowBottom) + 'px' : 0;
  231. },
  232. padBottom () {
  233. return !this.fixed ? this.numBottom + 'px' : 0;
  234. },
  235. // 过渡
  236. transition () {
  237. return this.isDownReset ? 'transform 300ms' : '';
  238. },
  239. translateY () {
  240. return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
  241. }
  242. },
  243. methods: {
  244. // 注册列表滚动事件,用于下拉刷新
  245. scroll (e) {
  246. e = e.detail;
  247. // 更新滚动条的位置
  248. this.scrollRealTop = e.scrollTop;
  249. // 更新滚动内容高度
  250. this.scrollHeight = e.scrollHeight;
  251. // 向上滑还是向下滑动
  252. const isScrollUp = e.scrollTop - this.preScrollY > 0;
  253. this.preScrollY = e.scrollTop;
  254. // 上滑 && 检查并触发上拉
  255. isScrollUp && this.triggerPullUp(true);
  256. // 回到顶部功能
  257. if (this.backTop) {
  258. // 返回顶部按钮的显示隐藏
  259. if (e.scrollTop >= this.numBackTopOffset) {
  260. this.isShowBackTop = true;
  261. } else {
  262. this.isShowBackTop = false;
  263. }
  264. }
  265. },
  266. // 注册列表touchstart事件,用于下拉刷新
  267. touchstart (e) {
  268. if (!this.pullDown) return;
  269. this.startPoint = this.getPoint(e); // 记录起点
  270. this.startTop = this.scrollRealTop; // 记录此时的滚动条位置
  271. this.lastPoint = this.startPoint; // 重置上次move的点
  272. this.maxTouchmoveY = this.bodyHeight - this.numDownBottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
  273. this.inTouchend = false; // 标记不是touchend
  274. },
  275. // 注册列表touchmove事件,用于下拉刷新
  276. touchmove (e) {
  277. if (!this.pullDown) return;
  278. if (!this.startPoint) return;
  279. // 节流
  280. const t = Date.now();
  281. if (this.moveTime && t - this.moveTime < this.moveTimeDiff) { // 小于节流时间,则不处理
  282. return;
  283. } else {
  284. this.moveTime = t;
  285. this.moveTimeDiff = 1000 / this.downFps;
  286. }
  287. let scrollRealTop = this.scrollRealTop; // 当前滚动条的距离
  288. let curPoint = this.getPoint(e); // 当前点
  289. let moveY = curPoint.y - this.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
  290. // (向下拉&&在顶部) scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
  291. // scroll-view滚动到顶部时,scrollTop不一定为0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
  292. if (moveY > 0 && (scrollRealTop <= 0 || (scrollRealTop <= this.numDownStartTop && scrollRealTop === this.startTop))) {
  293. // 可下拉的条件
  294. if (this.pullDown && !this.inTouchend && !this.isDownLoading && !this.isUpLoading) {
  295. // 下拉的角度是否在配置的范围内
  296. let x = Math.abs(this.lastPoint.x - curPoint.x);
  297. let y = Math.abs(this.lastPoint.y - curPoint.y);
  298. let z = Math.sqrt(x * x + y * y);
  299. if (z !== 0) {
  300. let angle = Math.asin(y / z) / Math.PI * 180; // 两点之间的角度,区间 [0,90]
  301. if (angle < this.downMinAngle) return; // 如果小于配置的角度,则不往下执行下拉刷新
  302. }
  303. // 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
  304. if (this.maxTouchmoveY > 0 && curPoint.y >= this.maxTouchmoveY) {
  305. this.inTouchend = true; // 标记执行touchend
  306. this.touchend(); // 提前触发touchend
  307. return;
  308. }
  309. this.preventDefault(e); // 阻止默认事件
  310. let diff = curPoint.y - this.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
  311. // 下拉距离 < 指定距离
  312. if (this.downHight < this.numDownOffset) {
  313. if (this.movetype !== 1) {
  314. this.movetype = 1; // 加入标记,保证只执行一次
  315. // 下拉的距离进入offset范围内那一刻的回调
  316. this.scrollAble = false; // 禁止下拉,避免抖动 (自定义mescroll组件时,此行不可删)
  317. this.isDownReset = false; // 不重置高度 (自定义mescroll组件时,此行不可删)
  318. this.isDownLoading = false; // 不显示加载中
  319. this.downText = this.pullingText; // 设置文本
  320. this.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
  321. }
  322. this.downHight += diff * this.downInOffsetRate; // 越往下,高度变化越小
  323. // 指定距离 <= 下拉距离
  324. } else {
  325. if (this.movetype !== 2) {
  326. this.movetype = 2; // 加入标记,保证只执行一次
  327. // 下拉的距离大于offset那一刻的回调
  328. this.scrollAble = false; // 禁止下拉,避免抖动 (自定义mescroll组件时,此行不可删)
  329. this.isDownReset = false; // 不重置高度 (自定义mescroll组件时,此行不可删)
  330. this.isDownLoading = false; // 不显示加载中
  331. this.downText = this.loosingText; // 设置文本
  332. this.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
  333. }
  334. if (diff > 0) { // 向下拉
  335. this.downHight += Math.round(diff * this.downOutOffsetRate); // 越往下,高度变化越小
  336. } else { // 向上收
  337. this.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
  338. }
  339. }
  340. let rate = this.downHight / this.numDownOffset; // 下拉区域当前高度与指定距离的比值
  341. // 下拉过程中的回调,滑动过程一直在执行; rate下拉区域当前高度与指定距离的比值(inOffset: rate<1; outOffset: rate>=1); downHight当前下拉区域的高度
  342. this.downRotate = 'rotate(' + 360 * rate + 'deg)'; // 设置旋转角度
  343. }
  344. }
  345. this.lastPoint = curPoint; // 记录本次移动的点
  346. },
  347. // 注册列表touchend事件,用于下拉刷新
  348. touchend (e) {
  349. if (!this.pullDown) return;
  350. // 如果下拉区域高度已改变,则需重置回来
  351. if (this.isMoveDown) {
  352. if (this.downHight >= this.numDownOffset) {
  353. // 符合触发刷新的条件
  354. this.triggerPullDown();
  355. } else {
  356. // 不符合的话 则重置
  357. this.downHight = 0;
  358. this.scrollAble = true; // 开启下拉 (自定义mescroll组件时,此行不可删)
  359. this.isDownReset = true; // 重置高度 (自定义mescroll组件时,此行不可删)
  360. this.isDownLoading = false; // 不显示加载中
  361. }
  362. this.movetype = 0;
  363. this.isMoveDown = false;
  364. } else if (this.scrollRealTop === this.startTop) { // 到顶/左/右/底的滑动事件
  365. const isScrollUp = this.getPoint(e).y - this.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
  366. // 上滑 && 检查并触发上拉
  367. isScrollUp && this.triggerPullUp(true);
  368. }
  369. },
  370. preventDefault (e) {
  371. // 小程序不支持e.preventDefault
  372. // app的bounce只能通过配置pages.json的style.app-plus.bounce为"none"来禁止
  373. // cancelable:是否可以被禁用; defaultPrevented:是否已经被禁用
  374. if (e && e.cancelable && !e.defaultPrevented) e.preventDefault();
  375. },
  376. // 点击回到顶部的按钮回调
  377. onBackTop () {
  378. this.isShowBackTop = false; // 回到顶部按钮需要先隐藏,再执行回到顶部,避免闪动
  379. this.scrollTo(0); // 执行回到顶部
  380. },
  381. // 点击失败重新加载
  382. onErrorClick () {
  383. this.isError = false;
  384. if (this.pullType === 'down') {
  385. this.triggerPullDown();
  386. } else if (this.pullType === 'up') {
  387. this.triggerPullUp();
  388. }
  389. },
  390. scrollTo (y) {
  391. this.scrollTop = this.scrollRealTop;
  392. this.$nextTick(() => {
  393. this.scrollTop = y;
  394. });
  395. },
  396. /* 根据点击滑动事件获取第一个手指的坐标 */
  397. getPoint (e) {
  398. if (!e) {
  399. return {
  400. x: 0,
  401. y: 0
  402. };
  403. }
  404. if (e.touches && e.touches[0]) {
  405. return {
  406. x: e.touches[0].pageX,
  407. y: e.touches[0].pageY
  408. };
  409. } else if (e.changedTouches && e.changedTouches[0]) {
  410. return {
  411. x: e.changedTouches[0].pageX,
  412. y: e.changedTouches[0].pageY
  413. };
  414. } else {
  415. return {
  416. x: e.clientX,
  417. y: e.clientY
  418. };
  419. }
  420. },
  421. /* 滚动条到底部的距离 */
  422. getScrollBottom () {
  423. return this.scrollHeight - this.getClientHeight() - this.scrollRealTop;
  424. },
  425. /* 滚动容器的高度 */
  426. getClientHeight (isReal) {
  427. let h = this.clientHeight || 0;
  428. if (h === 0 && isReal !== true) { // 未获取到容器的高度,可临时取body的高度 (可能会有误差)
  429. h = this.bodyHeight;
  430. }
  431. return h;
  432. },
  433. /* 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页) */
  434. refreshClientHeight () {
  435. if (!this.isExec) {
  436. this.isExec = true; // 避免多次获取
  437. this.$nextTick(() => { // 确保dom已渲染
  438. uni.createSelectorQuery().in(this).select('#' + this.scrollId).boundingClientRect(data => {
  439. this.isExec = false;
  440. if (data) {
  441. this.clientHeight = data.height;
  442. } else if (this.clientNum != 3) { // 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次
  443. this.clientNum = this.clientNum == 0 ? 1 : this.clientNum + 1;
  444. setTimeout(() => {
  445. this.refreshClientHeight();
  446. }, this.clientNum * 100);
  447. }
  448. }).exec();
  449. });
  450. }
  451. },
  452. /* 显示下拉进度布局 */
  453. showDownLoading () {
  454. this.isEmpty = false;
  455. this.isError = false;
  456. this.isFinish = false;
  457. this.isDownLoading = true; // 显示加载中
  458. this.downHight = this.numDownOffset; // 更新下拉区域高度
  459. this.scrollAble = true; // 开启下拉 (自定义mescroll组件时,此行不可删)
  460. this.isDownReset = true; // 重置高度 (自定义mescroll组件时,此行不可删)
  461. this.downText = this.loadingText; // 设置文本
  462. },
  463. /* 显示上拉加载中 */
  464. showUpLoading () {
  465. this.isEmpty = false;
  466. this.isError = false;
  467. this.isFinish = false;
  468. this.isUpLoading = true;
  469. },
  470. /* 结束下拉刷新 */
  471. hideDownLoading () {
  472. this.$nextTick(() => {
  473. this.downHight = 0;
  474. this.scrollAble = true; // 开启下拉 (自定义mescroll组件时,此行不可删)
  475. this.isDownReset = true; // 重置高度 (自定义mescroll组件时,此行不可删)
  476. this.isDownLoading = false; // 不显示加载中
  477. this.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
  478. this.scrollHeight = 0;// 重置滚动区域,使数据不满屏时仍可检查触发翻页
  479. });
  480. },
  481. /* 结束上拉加载 */
  482. hideUpLoading () {
  483. this.$nextTick(() => {
  484. this.isUpLoading = false;
  485. });
  486. },
  487. /* 触发下拉刷新 */
  488. triggerPullDown () {
  489. if (this.pullDown && !this.isDownLoading && !this.isUpLoading) {
  490. // 下拉加载中...
  491. this.showDownLoading(); // 下拉刷新中...
  492. this.page = 1; // 预先加一页
  493. this.pullType = 'down';
  494. this.pullDown && this.pullDown.call(this.$parent, this);
  495. }
  496. },
  497. /* 触发上拉加载 */
  498. triggerPullUp (isCheck) {
  499. if (this.pullUp && !this.isUpLoading && !this.isDownLoading && !this.isError && !this.isFinish) {
  500. // 是否校验在底部; 默认不校验
  501. if (isCheck && this.getScrollBottom() > this.numUpOffset) return;
  502. // 上拉加载中...
  503. this.showUpLoading();
  504. this.page++;
  505. this.pullType = 'up';
  506. this.pullUp && this.pullUp.call(this.$parent, this);
  507. // 更新容器的高度
  508. this.refreshClientHeight();
  509. }
  510. },
  511. refresh () {
  512. this.page = 0;
  513. this.isEmpty = false;
  514. this.isError = false;
  515. this.isFinish = false;
  516. this.isDownLoading = false;
  517. this.isUpLoading = false;
  518. this.scrollTo(0);
  519. if (this.pullDown) {
  520. this.triggerPullDown();
  521. } else if (this.pullUp) {
  522. this.triggerPullUp();
  523. }
  524. },
  525. /* 正常加载成功 */
  526. success () {
  527. this.hideDownLoading();
  528. this.hideUpLoading();
  529. },
  530. /* 加载失败 */
  531. error () {
  532. this.hideDownLoading();
  533. this.hideUpLoading();
  534. this.page--;
  535. this.isError = true;
  536. },
  537. /* 没有数据 */
  538. empty () {
  539. this.hideDownLoading();
  540. this.hideUpLoading();
  541. this.isEmpty = true;
  542. this.isFinish = true;
  543. },
  544. /* 全部数据加载完毕 */
  545. finish (isShowFinishText = true) {
  546. this.hideDownLoading();
  547. this.hideUpLoading();
  548. this.isFinish = true;
  549. this.isShowFinishText = !!isShowFinishText;
  550. }
  551. },
  552. // 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
  553. created () {
  554. // 设置高度
  555. uni.getSystemInfo({
  556. success: (res) => {
  557. if (res.windowTop) this.windowTop = res.windowTop; // 修正app和H5的top值
  558. if (res.windowBottom) this.windowBottom = res.windowBottom; // 修正app和H5的bottom值
  559. this.bodyHeight = res.windowHeight;// 使down的bottomOffset生效
  560. }
  561. });
  562. },
  563. mounted () {
  564. // 设置容器的高度
  565. this.refreshClientHeight = this.refreshClientHeight.bind(this);
  566. uni.onWindowResize(this.refreshClientHeight);
  567. this.refreshClientHeight();
  568. },
  569. beforeDestroy () {
  570. uni.offWindowResize(this.refreshClientHeight);
  571. }
  572. };
  573. </script>
  574. <style lang="scss">
  575. .s-pull-scroll {
  576. height: 100%;
  577. -webkit-overflow-scrolling: touch;
  578. .s-pull-scroll-view {
  579. position: relative;
  580. width: 100%;
  581. height: 100%;
  582. overflow-y: auto;
  583. box-sizing: border-box;
  584. }
  585. /* 定位的方式固定高度 */
  586. .is-fixed {
  587. z-index: 1;
  588. // position: fixed;
  589. top: 0;
  590. left: 0;
  591. right: 0;
  592. bottom: 0;
  593. width: auto;
  594. height: auto;
  595. }
  596. .s-pull-down-wrap,
  597. .s-pull-up-wrap,
  598. .s-pull-tip-wrap {
  599. display: flex;
  600. justify-content: center;
  601. align-items: center;
  602. font-size: 28rpx;
  603. color: #969799;
  604. }
  605. .s-pull-down-wrap {
  606. position: absolute;
  607. left: 0;
  608. width: 100%;
  609. transform: translateY(-100%);
  610. }
  611. .s-pull-up-wrap,
  612. .s-pull-tip-wrap {
  613. height: 100rpx;
  614. }
  615. /* 旋转loading */
  616. .s-pull-loading-icon {
  617. width: 30rpx;
  618. height: 30rpx;
  619. display: inline-block;
  620. vertical-align: middle;
  621. border-radius: 50%;
  622. border: 2rpx solid #c8c9cc;
  623. border-bottom-color: transparent;
  624. box-sizing: border-box;
  625. &:first-child {
  626. margin-right: 16rpx;
  627. }
  628. }
  629. /* 旋转动画 */
  630. .s-pull-loading-rotate {
  631. animation: s-pull-loading-rotate 0.6s linear infinite;
  632. }
  633. @keyframes s-pull-loading-rotate {
  634. 0% {
  635. transform: rotate(0deg);
  636. }
  637. 100% {
  638. transform: rotate(360deg);
  639. }
  640. }
  641. /* 回到顶部的按钮 */
  642. .s-pull-back-top {
  643. position: relative;
  644. z-index: 99;
  645. opacity: 0;
  646. pointer-events: none;
  647. transition: opacity 0.3s linear;
  648. &.show {
  649. opacity: 1;
  650. pointer-events: auto;
  651. }
  652. }
  653. .default-back-top {
  654. position: fixed;
  655. right: 20rpx;
  656. bottom: calc(var(--window-bottom) + 15rpx);
  657. img {
  658. width: 72rpx;
  659. height: 72rpx;
  660. border-radius: 50%;
  661. }
  662. }
  663. }
  664. </style>