load-refresh.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. <template>
  2. <view class="load-refresh">
  3. <!-- 刷新动画,可自定义,占高100rpx -->
  4. <view class="animation" :style="{'--color': color}">
  5. <view v-if="!playState" class="remind">
  6. {{moving ? '↑ 松开释放' : '↓ 下拉刷新'}}
  7. </view>
  8. <!-- <view v-if="playState && refreshType === 'hollowDots'" class="refresh hollow-dots-spinner">
  9. <view class="dot"></view>
  10. <view class="dot"></view>
  11. <view class="dot"></view>
  12. </view>
  13. <view v-if="playState && refreshType === 'halfCircle'" class="refresh half-circle-spinner">
  14. <view class="circle circle-1"></view>
  15. <view class="circle circle-2"></view>
  16. </view>
  17. <view v-if="playState && refreshType === 'swappingSquares'" class="refresh swapping-squares-spinner">
  18. <view class="square"></view>
  19. <view class="square"></view>
  20. <view class="square"></view>
  21. <view class="square"></view>
  22. </view> -->
  23. </view>
  24. <!-- 数据列表块 -->
  25. <view
  26. class="cover-container"
  27. :style="[{
  28. background: backgroundCover,
  29. transform: coverTransform,
  30. transition: coverTransition,
  31. }]"
  32. @touchstart="coverTouchstart"
  33. @touchmove="coverTouchmove"
  34. @touchend="coverTouchend">
  35. <scroll-view scroll-y scroll-x="false" class="list" :scroll-top="scrollTop" :style="getHeight">
  36. <!-- 数据集插槽 -->
  37. <slot name="content-list"></slot>
  38. <!-- 上拉加载 -->
  39. <view class="load-more">{{loadText}}</view>
  40. </scroll-view>
  41. </view>
  42. </view>
  43. </template>
  44. <script>
  45. export default {
  46. name: 'loadRefresh',
  47. props: {
  48. isRefresh: {
  49. type: Boolean,
  50. default: true
  51. },
  52. refreshType: {
  53. type: String,
  54. default: 'hollowDots'
  55. },
  56. fixedHeight: {
  57. type: String,
  58. default: '0'
  59. },
  60. heightReduce: {
  61. type: String,
  62. default: '0'
  63. },
  64. color: {
  65. type: String,
  66. default: '#04C4C4'
  67. },
  68. backgroundCover: {
  69. type: String,
  70. default: 'white'
  71. },
  72. currentPage: {
  73. type: Number,
  74. default: 0
  75. },
  76. totalPages: {
  77. type: Number,
  78. default: 0
  79. }
  80. },
  81. data() {
  82. return {
  83. startY: 0,
  84. moveY: 0,
  85. updating: false, // 数据更新状态(true: 更新中)
  86. updateType: true, // 数据更新类型(true: 下拉刷新: false: 加载更多)
  87. moving: false,
  88. scrollTop: -1,
  89. coverTransform: 'translateY(0px)',
  90. coverTransition: '0s',
  91. playState: false // 动画的状态 暂停 paused/开始 running
  92. }
  93. },
  94. computed: {
  95. // 计算组件所占屏幕高度
  96. getHeight() {
  97. // rpx = px / uni.getSystemInfoSync().windowWidth * 750
  98. if (Number(this.fixedHeight)) {
  99. return `height: ${this.fixedHeight}rpx;`
  100. } else {
  101. let height = uni.getSystemInfoSync().windowHeight - uni.getSystemInfoSync().windowTop - uni.getSystemInfoSync().windowBottom - 60;
  102. let bottombar=uni.getSystemInfoSync().windowBottom;
  103. //return `top:0;height: ${height}px;margin-bottom: ${bottombar}px;position:relative;`
  104. return `height: ${height}px;`
  105. }
  106. },
  107. // 判断loadText,可以根据需求自定义
  108. loadText() {
  109. const { currentPage, totalPages, updating, updateType } = this
  110. if (!updateType && updating) {
  111. return '加载中...'
  112. } else if (currentPage < totalPages) {
  113. return '上拉加载更多'
  114. } else {
  115. return '已经到底啦~'
  116. }
  117. }
  118. },
  119. methods: {
  120. // 根据currentPage和totalPages的值来判断 是否触发@loadMore
  121. loadMore() {
  122. const { currentPage, totalPages } = this
  123. if (!this.updating && currentPage < totalPages) {
  124. this.updating = true
  125. this.updateType = false
  126. this.$emit('loadMore')
  127. }
  128. },
  129. // 回弹效果
  130. coverTouchstart(e) {
  131. if (!this.isRefresh) {
  132. return
  133. }
  134. this.coverTransition = 'transform .1s linear'
  135. this.startY = e.touches[0].clientY
  136. },
  137. coverTouchmove(e) {
  138. if (!this.isRefresh || this.updating) {
  139. return
  140. }
  141. this.moveY = e.touches[0].clientY
  142. let moveDistance = this.moveY - this.startY
  143. if (moveDistance <= 50) {
  144. this.coverTransform = `translateY(${moveDistance}px)`
  145. }
  146. this.moving = moveDistance >= 50
  147. },
  148. coverTouchend() {
  149. if (!this.isRefresh || this.updating) {
  150. return
  151. }
  152. if (this.moving) {
  153. this.runRefresh()
  154. } else {
  155. this.coverTransition = 'transform 0.3s cubic-bezier(.21,1.93,.53,.64)'
  156. this.coverTransform = 'translateY(0px)'
  157. }
  158. },
  159. runRefresh() {
  160. this.scrollTop = 0
  161. this.coverTransition = 'transform .1s linear'
  162. this.coverTransform = 'translateY(50px)'
  163. this.playState = true
  164. this.updating = true
  165. this.updateType = true
  166. this.$emit('refresh')
  167. },
  168. completed() {
  169. if (this.updateType) {
  170. // 下拉刷新
  171. this.moving = false
  172. this.scrollTop = -1
  173. this.coverTransition = 'transform 0.3s cubic-bezier(.21,1.93,.53,.64)'
  174. this.coverTransform = 'translateY(0px)'
  175. setTimeout(() => {
  176. this.playState = false
  177. }, 300)
  178. }
  179. this.updating = false
  180. }
  181. }
  182. }
  183. </script>
  184. <style lang="scss" scoped>
  185. $color: var(--color);
  186. .load-refresh{
  187. margin: 0;
  188. padding: 0;
  189. width: 100%;
  190. .cover-container{
  191. width: 100%;
  192. margin-top: -100rpx;
  193. .list{
  194. width: 100%;
  195. .load-more{
  196. font-size: 20rpx;
  197. text-align: center;
  198. color: #AAAAAA;
  199. padding: 16rpx;
  200. }
  201. }
  202. }
  203. }
  204. /* 动画 */
  205. .animation {
  206. width: 100%;
  207. height: 100rpx;
  208. .remind {
  209. width: 100%;
  210. height: 100rpx;
  211. text-align: center;
  212. line-height: 100rpx;
  213. }
  214. .refresh {
  215. width: 100%;
  216. height: 100rpx;
  217. display: flex;
  218. align-items: center;
  219. justify-content: center;
  220. box-sizing: border-box;
  221. view {
  222. // animation-play-state: $playState!important;
  223. }
  224. }
  225. /* HollowDots */
  226. .hollow-dots-spinner .dot {
  227. width: 30rpx;
  228. height: 30rpx;
  229. margin: 0 calc(30rpx / 2);
  230. border: calc(30rpx / 5) solid $color;
  231. border-radius: 50%;
  232. float: left;
  233. transform: scale(0);
  234. animation: hollowDots 1000ms ease infinite 0ms;
  235. }
  236. .hollow-dots-spinner .dot:nth-child(1) {
  237. animation-delay: calc(300ms * 1);
  238. }
  239. .hollow-dots-spinner .dot:nth-child(2) {
  240. animation-delay: calc(300ms * 2);
  241. }
  242. .hollow-dots-spinner .dot:nth-child(3) {
  243. animation-delay: calc(300ms * 3);
  244. }
  245. @keyframes hollowDots {
  246. 50% {
  247. transform: scale(1);
  248. opacity: 1;
  249. }
  250. 100% {
  251. opacity: 0;
  252. }
  253. }
  254. /* halfCircle */
  255. .half-circle-spinner .circle {
  256. content: "";
  257. position: absolute;
  258. width: 60rpx;
  259. height: 60rpx;
  260. border-radius: 100%;
  261. border: calc(60rpx / 10) solid transparent;
  262. }
  263. .half-circle-spinner .circle.circle-1 {
  264. border-top-color: $color;
  265. animation: halfCircle 1s infinite;
  266. }
  267. .half-circle-spinner .circle.circle-2 {
  268. border-bottom-color: $color;
  269. animation: halfCircle 1s infinite alternate;
  270. }
  271. @keyframes halfCircle {
  272. 0% {
  273. transform: rotate(0deg);
  274. }
  275. 100%{
  276. transform: rotate(360deg);
  277. }
  278. }
  279. /* swappingSquares */
  280. .swapping-squares-spinner {
  281. position: relative;
  282. }
  283. .swapping-squares-spinner .square {
  284. height: calc(65rpx * 0.25 / 1.3);
  285. width: calc(65rpx * 0.25 / 1.3);
  286. animation-duration: 1000ms;
  287. border: calc(65rpx * 0.04 / 1.3) solid $color;
  288. margin-right: auto;
  289. margin-left: auto;
  290. position: absolute;
  291. animation-iteration-count: infinite;
  292. }
  293. .swapping-squares-spinner .square:nth-child(1) {
  294. animation-name: swappingSquares-child-1;
  295. animation-delay: 500ms;
  296. }
  297. .swapping-squares-spinner .square:nth-child(2) {
  298. animation-name: swappingSquares-child-2;
  299. animation-delay: 0ms;
  300. }
  301. .swapping-squares-spinner .square:nth-child(3) {
  302. animation-name: swappingSquares-child-3;
  303. animation-delay: 500ms;
  304. }
  305. .swapping-squares-spinner .square:nth-child(4) {
  306. animation-name: swappingSquares-child-4;
  307. animation-delay: 0ms;
  308. }
  309. @keyframes swappingSquares-child-1 {
  310. 50% {
  311. transform: translate(150%,150%) scale(2,2);
  312. }
  313. }
  314. @keyframes swappingSquares-child-2 {
  315. 50% {
  316. transform: translate(-150%,150%) scale(2,2);
  317. }
  318. }
  319. @keyframes swappingSquares-child-3 {
  320. 50% {
  321. transform: translate(-150%,-150%) scale(2,2);
  322. }
  323. }
  324. @keyframes swappingSquares-child-4 {
  325. 50% {
  326. transform: translate(150%,-150%) scale(2,2);
  327. }
  328. }
  329. }
  330. </style>