index.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. import baseComponent from '../helpers/baseComponent'
  2. import classNames from '../helpers/classNames'
  3. const defaultStyle = 'transition: transform .4s; transform: translate3d(0px, 0px, 0px) scale(1);'
  4. baseComponent({
  5. properties: {
  6. prefixCls: {
  7. type: String,
  8. value: 'wux-refresher',
  9. },
  10. pullingIcon: {
  11. type: String,
  12. value: '',
  13. },
  14. pullingText: {
  15. type: String,
  16. value: '下拉刷新',
  17. },
  18. refreshingIcon: {
  19. type: String,
  20. value: '',
  21. },
  22. refreshingText: {
  23. type: String,
  24. value: '正在刷新',
  25. },
  26. disablePullingRotation: {
  27. type: Boolean,
  28. value: false,
  29. },
  30. distance: {
  31. type: Number,
  32. value: 30,
  33. },
  34. prefixLCls: {
  35. type: String,
  36. value: 'wux-loader'
  37. },
  38. isShowLoadingText: {
  39. type: Boolean,
  40. value: false
  41. },
  42. loadingText: {
  43. type: String,
  44. value: '正在加载'
  45. },
  46. loadNoDataText: {
  47. type: String,
  48. value: '没有更多数据'
  49. },
  50. scrollTop: {
  51. type: Number,
  52. value: 0,
  53. observer: function (n) {
  54. let that = this
  55. // 获取节点高度
  56. const query = wx.createSelectorQuery();
  57. query.select(`#${this.id}`).boundingClientRect(function (res) {
  58. that.setData({
  59. newContentHeight: res.height
  60. })
  61. }).exec()
  62. const {
  63. newContentHeight,
  64. oldContentHeight,
  65. windowHeight,
  66. distance,
  67. loading,
  68. noData
  69. } = this.data
  70. if (windowHeight && !this.isRefreshing()) {
  71. // 到临界点时触发上拉加载
  72. // 防止节点高度一致时引发重复加载
  73. if (
  74. n > newContentHeight - windowHeight - (distance * 1.5) &&
  75. loading === false &&
  76. noData === false && newContentHeight !== oldContentHeight
  77. ) {
  78. this.setData({
  79. loading: true,
  80. refreshing: false,
  81. oldContentHeight: newContentHeight
  82. })
  83. this.triggerEvent('loadmore')
  84. } else if (
  85. loading === false &&
  86. noData === false
  87. ) {
  88. // 隐藏上拉加载动画
  89. this.hide()
  90. } else if(loading === true) {
  91. // 如果在加载中,保持内容的高度一致,以此来防止临界点重复加载
  92. this.setData({
  93. oldContentHeight: newContentHeight
  94. })
  95. }
  96. this.deactivate()
  97. }
  98. }
  99. },
  100. },
  101. data: {
  102. style: defaultStyle,
  103. visible: false,
  104. active: false,
  105. refreshing: false,
  106. tail: false,
  107. lVisible: false,
  108. noData: false, // 是否没有更多数据
  109. windowHeight: 0, // 窗口高度
  110. newContentHeight: 0, // 新节点内容高度
  111. oldContentHeight: 0, // 旧节点内容高度
  112. loading: false, // 判断是否正在加载
  113. },
  114. computed: {
  115. classes() {
  116. const {
  117. prefixCls,
  118. pullingText,
  119. pullingIcon,
  120. disablePullingRotation,
  121. refreshingText,
  122. refreshingIcon,
  123. visible,
  124. active,
  125. refreshing,
  126. tail,
  127. prefixLCls,
  128. loading,
  129. noData,
  130. } = this.data
  131. const wrap = classNames(prefixCls, {
  132. [`${prefixCls}--hidden`]: !visible,
  133. [`${prefixCls}--visible`]: visible,
  134. [`${prefixCls}--active`]: active,
  135. [`${prefixCls}--refreshing`]: refreshing,
  136. [`${prefixCls}--refreshing-tail`]: tail,
  137. })
  138. const content = classNames(`${prefixCls}__content`, {
  139. [`${prefixCls}__content--text`]: pullingText || refreshingText,
  140. })
  141. const iconPulling = classNames(`${prefixCls}__icon-pulling`, {
  142. [`${prefixCls}__icon-pulling--disabled`]: disablePullingRotation,
  143. })
  144. const textPulling = `${prefixCls}__text-pulling`
  145. const iconRefreshing = `${prefixCls}__icon-refreshing`
  146. const textRefreshing = `${prefixCls}__text-refreshing`
  147. const pIcon = pullingIcon || `${prefixCls}__icon--arrow-down`
  148. const rIcon = refreshingIcon || `${prefixCls}__icon--refresher`
  149. const lWrap = classNames(prefixLCls, {
  150. [`${prefixLCls}--hidden`]: !loading,
  151. [`${prefixLCls}--visible`]: loading,
  152. [`${prefixLCls}--end`]: noData,
  153. })
  154. const lContent = `${prefixLCls}__content`
  155. return {
  156. wrap,
  157. content,
  158. iconPulling,
  159. textPulling,
  160. iconRefreshing,
  161. textRefreshing,
  162. pIcon,
  163. rIcon,
  164. lWrap,
  165. lContent,
  166. }
  167. },
  168. },
  169. methods: {
  170. /**
  171. * 显示
  172. */
  173. activate() {
  174. this.setData({
  175. style: defaultStyle,
  176. visible: true,
  177. })
  178. },
  179. /**
  180. * 隐藏
  181. */
  182. deactivate() {
  183. if (this.activated) this.activated = false
  184. this.setData({
  185. style: defaultStyle,
  186. visible: false,
  187. active: false,
  188. refreshing: false,
  189. tail: false,
  190. })
  191. },
  192. /**
  193. * 正在刷新
  194. */
  195. refreshing() {
  196. this.setData({
  197. style: 'transition: transform .4s; transform: translate3d(0, 50px, 0) scale(1);',
  198. visible: true,
  199. active: true,
  200. refreshing: true,
  201. // 刷新时重新初始化加载状态
  202. loading: false,
  203. noData: false,
  204. newContentHeight: 0,
  205. oldContentHeight: 0,
  206. lVisible: false,
  207. })
  208. },
  209. /**
  210. * 刷新后隐藏动画
  211. */
  212. tail() {
  213. this.setData({
  214. visible: true,
  215. active: true,
  216. refreshing: true,
  217. tail: true,
  218. })
  219. },
  220. /**
  221. * 加载后隐藏动画
  222. */
  223. hide() {
  224. this.setData({
  225. lVisible: false,
  226. })
  227. },
  228. /**
  229. * 正在下拉
  230. * @param {Number} diffY 距离
  231. */
  232. move(diffY) {
  233. const style = `transition-duration: 0s; transform: translate3d(0, ${diffY}px, 0) scale(1);`
  234. const className = diffY < this.data.distance ? 'visible' : 'active'
  235. this.setData({
  236. style,
  237. [className]: true,
  238. })
  239. },
  240. /**
  241. * 判断是否正在刷新
  242. */
  243. isRefreshing() {
  244. return this.data.refreshing
  245. },
  246. /**
  247. * 判断是否正在加载
  248. */
  249. isLoading() {
  250. return this.data.loading
  251. },
  252. /**
  253. * 获取触摸点坐标
  254. */
  255. getTouchPosition(e) {
  256. return {
  257. x: e.changedTouches[0].pageX,
  258. y: e.changedTouches[0].pageY,
  259. }
  260. },
  261. /**
  262. * 创建定时器
  263. */
  264. requestAnimationFrame(callback) {
  265. let currTime = new Date().getTime()
  266. let timeToCall = Math.max(0, 16 - (currTime - this.lastTime))
  267. let timeout = setTimeout(() => {
  268. callback.bind(this)(currTime + timeToCall)
  269. }, timeToCall)
  270. this.lastTime = currTime + timeToCall
  271. return timeout
  272. },
  273. /**
  274. * 清空定时器
  275. */
  276. cancelAnimationFrame(timeout) {
  277. clearTimeout(timeout)
  278. },
  279. /**
  280. * 下拉刷新完成后的函数
  281. */
  282. finishPullToRefresh() {
  283. setTimeout(() => {
  284. this.requestAnimationFrame(this.tail)
  285. setTimeout(() => this.deactivate(), 200)
  286. }, 200)
  287. },
  288. /**
  289. * 上拉加载完成后的函数
  290. */
  291. finishLoadmore(bool) {
  292. if (bool === true) {
  293. setTimeout(() => {
  294. this.setData({
  295. noData: true,
  296. loading: false,
  297. })
  298. }, 200)
  299. } else {
  300. setTimeout(() => {
  301. this.setData({
  302. loading: false
  303. })
  304. this.requestAnimationFrame(this.hide)
  305. setTimeout(() => this.deactivate(), 200)
  306. }, 200)
  307. }
  308. },
  309. /**
  310. * 手指触摸动作开始
  311. */
  312. bindtouchstart(e) {
  313. if (this.isRefreshing() || this.isLoading()) return false
  314. const p = this.getTouchPosition(e)
  315. this.start = p
  316. this.diffX = this.diffY = 0
  317. this.activate()
  318. },
  319. /**
  320. * 手指触摸后移动
  321. */
  322. bindtouchmove(e) {
  323. if (!this.start || this.isRefreshing() || this.isLoading()) return false
  324. const p = this.getTouchPosition(e)
  325. this.diffX = p.x - this.start.x
  326. this.diffY = p.y - this.start.y
  327. if (this.diffY < 0) return false
  328. this.diffY = Math.pow(this.diffY, 0.8)
  329. if (!this.activated && this.diffY > this.data.distance) {
  330. this.activated = true
  331. this.triggerEvent('pulling')
  332. } else if (this.activated && this.diffY < this.data.distance) {
  333. this.activated = false
  334. }
  335. this.move(this.diffY)
  336. },
  337. /**
  338. * 手指触摸动作结束
  339. */
  340. bindtouchend(e) {
  341. this.start = false
  342. if (this.diffY <= 0 || this.isRefreshing() || this.isLoading()) return false
  343. this.deactivate()
  344. if (Math.abs(this.diffY) >= this.data.distance) {
  345. this.refreshing()
  346. this.triggerEvent('refresh')
  347. }
  348. },
  349. },
  350. created() {
  351. this.lastTime = 0
  352. this.activated = false
  353. },
  354. attached() {
  355. let that = this
  356. wx.getSystemInfo({
  357. success: function (res) {
  358. that.setData({
  359. windowHeight: res.windowHeight
  360. })
  361. }
  362. });
  363. }
  364. })