├── .gitignore ├── README.md ├── lib ├── index.js ├── index.json ├── index.wxml └── index.wxss ├── package.json └── screenshot └── 1.gif /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | ./dist/ 4 | dist/* 5 | dist/ 6 | .wepycache 7 | .idea 8 | npm-debug.log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 微信小程序 基于scroll-view 下拉刷新 2 | 3 | 4 | ## 说明 5 | 由于官方scroll-view组件只有滚到顶部刷新的机制,对用户来说不是很友好,所以做一个基于scroll-view的下拉刷新组件(该组件支持原生引用、wexp框架、wepy框架) 6 | 7 | ## 展示 8 | ![Android](./screenshot/1.gif) 9 | ## 使用 10 | 11 | ### 安装组件 12 | ``` 13 | npm i weapp-scroll-view-refresh -S --production 14 | ``` 15 | 16 | ### 使用示例 17 | ```json 18 | usingComponents: { 19 | 'k-scroll': 'weapp-scroll-view-refresh/index' 20 | } 21 | ``` 22 | 23 | ```html 24 | 30 | content 31 | 32 | ``` 33 | 34 | ## API 35 | ### 组件参数 36 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 37 | |-----------|---------------------------------|-----------|-----------|-------------| 38 | | lowerThresHold | 距底部多远时触发 scrolltolower 事件 | `Number` | ` ` | `50` | 39 | | scrollWithAnimation | 在设置滚动条位置时使用动画过渡 | `Boolean` | `true` `false` | `false` | 40 | | enableBackToTop | iOS点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部,只支持竖向 | `Boolean` | `true` `false` | `false` | 41 | | scrollIntoView | 值应为某子元素id(id不能以数字开头)。设置哪个方向可滚动,则在哪个方向滚动到该元素 | `String` | ` ` | ` ` | 42 | 43 | 44 | ### Event 45 | 46 | | 事件名 | 说明 | 参数 | 47 | |-----------|-----------|-----------| 48 | | bind:scroll | 组件滚动触发该事件 | event.detail: 回传滚动的高度 | 49 | | bind:scrollToLower | 组件滚动到底部触发该事件 | 无回传参数 | 50 | | bind:refresh | 组件刷新操作的时候触发该事件 | 无回传参数 | 51 | 52 | ### 函数 53 | 54 | | 事件名 | 说明 | 参数 | 55 | |-----------|-----------|-----------| 56 | | reset | 重置刷新状态 | `-` | 57 | 58 | 59 | ## 更多说明 60 | 参考[原版插件](https://github.com/Chaunjie/weapp-scroll-view-refresh)。 -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | Component({ 2 | data: { 3 | scrollY: true, 4 | touchYDelta: '', 5 | isLoading: false, 6 | loadWrapH: '', 7 | winfactor: 0.2, 8 | translateVal: '', 9 | isMoved: false, 10 | firstTouchY: '', 11 | initialScroll: '', 12 | friction: 2.5, 13 | scrollTop: 0, 14 | triggerDistance: 100, 15 | className: '', 16 | animationData: {}, 17 | animation: {}, 18 | system: '', 19 | brand: '', 20 | loader: { 21 | height: 500 22 | }, 23 | time: '', 24 | text: '下拉可以刷新' 25 | }, 26 | externalClasses: ['custom-class'], 27 | options: { 28 | multipleSlots: true 29 | }, 30 | properties: { 31 | lowerThresHold: { 32 | type: Number, 33 | value: 50 34 | }, 35 | scrollWithAnimation: { 36 | type: Boolean, 37 | value: false 38 | }, 39 | enableBackToTop: { 40 | type: Boolean, 41 | value: false 42 | }, 43 | scrollIntoView: { 44 | type: String, 45 | value: '' 46 | } 47 | }, 48 | methods: { 49 | scroll (e) { 50 | this.setData({ 51 | scrollTop: e.detail.scrollTop 52 | }) 53 | this.triggerEvent('scroll', e.detail.scrollTop) 54 | }, 55 | scrollToLower () { 56 | this.triggerEvent('lower') 57 | }, 58 | touchstart (ev) { 59 | const {isLoading, scrollTop} = this.data 60 | if (isLoading) { 61 | return 62 | } 63 | const touchobj = ev.touches[0] 64 | this.setData({ 65 | isMoved: false, 66 | sDuration: '0ms', 67 | touchYDelta: '', 68 | firstTouchY: parseInt(touchobj.clientY), 69 | initialScroll: scrollTop 70 | }) 71 | }, 72 | touchmove (ev) { 73 | const {isLoading} = this.data 74 | if (isLoading) { 75 | return 76 | } 77 | let touchobj = ev.touches[0] 78 | let touchY = parseInt(touchobj.clientY) 79 | let touchYDelta = touchY - this.data.firstTouchY 80 | if (this.data.initialScroll > 0 || this.data.scrollTop > 0 || this.data.scrollTop === 0 && touchYDelta < 0) { 81 | this.setData({ 82 | firstTouchY: touchY 83 | }) 84 | return 85 | } 86 | /* eslint-enable */ 87 | const yDelta = this.data.brand === 'devtools' ? touchYDelta ** 0.85 : this.data.system === 'ios' ? touchYDelta ** 0.5 : touchYDelta ** 0.85 88 | // let translateVal = yDelta 89 | this.data.animation.translate3d(0, yDelta, 0).step() 90 | let obj = touchYDelta >= this.data.triggerDistance ? { 91 | className: 'refresh-pull-up', 92 | // text: '松开可以刷新' 93 | } : { 94 | className: 'refresh-pull-down', 95 | // text: '下拉可以刷新' 96 | } 97 | this.setData({ 98 | touchYDelta: touchYDelta, 99 | animationData: this.data.animation.export(), 100 | isMoved: true, 101 | ...obj 102 | }) 103 | }, 104 | touchend (ev) { 105 | if (this.data.isLoading || !this.data.isMoved) { 106 | this.setData({ 107 | isMoved: false 108 | }) 109 | return 110 | } 111 | // 根据下拉高度判断是否加载 112 | if (this.data.touchYDelta >= this.data.triggerDistance) { 113 | this.data.animation.translate3d(0, this.data.loader.height, 0).step() 114 | this.setData({ 115 | isLoading: true, 116 | scrollY: false, 117 | animationData: this.data.animation.export(), 118 | className: 'refreshing', 119 | text: '正在刷新...' 120 | }) 121 | this.triggerEvent('refresh', 'success') 122 | } else { 123 | this.data.animation.translate3d(0, 0, 0).step({duration: 300}) 124 | this.setData({ 125 | animationData: this.data.animation.export() 126 | }) 127 | // this.triggerEvent('refresh', 'error'); 128 | } 129 | this.setData({ 130 | isMoved: false 131 | }) 132 | }, 133 | reset () { 134 | this.setData({ 135 | isLoading: false, 136 | scrollY: true, 137 | className: 'refresh-pull-up', 138 | text: '下拉可以刷新' 139 | }, () => { 140 | this.data.animation.translate3d(0, 0, 0).step({duration: 300}) 141 | const time = this.getTime() 142 | this.setData({ 143 | animationData: this.data.animation.export(), 144 | className: 'refresh-pull-down', 145 | time: time 146 | }) 147 | }) 148 | }, 149 | throttle (fn, delay) { 150 | let allowSample = true 151 | return function(e) { 152 | if (allowSample) { 153 | allowSample = false 154 | setTimeout(function() { allowSample = true }, delay) 155 | fn(e) 156 | } 157 | } 158 | }, 159 | getTime() { 160 | const date = new Date() 161 | return `${date.getFullYear()}-${this.getFriendlyTime(date.getMonth() + 1)}-${this.getFriendlyTime(date.getDate())} ${this.getFriendlyTime(date.getHours())}:${this.getFriendlyTime(date.getMinutes())}:${this.getFriendlyTime(date.getSeconds())}` 162 | }, 163 | getFriendlyTime (time) { 164 | return time < 10 ? '0' + time : time 165 | }, 166 | detached () { 167 | console.log(66666) 168 | } 169 | }, 170 | ready () { 171 | let animation = wx.createAnimation({ 172 | duration: 0, 173 | timingFunction: 'linear' 174 | }) 175 | let system = 'android' 176 | const systemInfo = wx.getSystemInfoSync() 177 | if (/iPhone/.test(systemInfo.model)) { 178 | system = 'ios' 179 | } 180 | const time = this.getTime() 181 | this.setData({ 182 | time: time, 183 | system: system, 184 | animation: animation, 185 | brand: systemInfo.brand, 186 | animationData: animation.export() 187 | }) 188 | wx.createSelectorQuery().in(this).selectAll('.refresh-load').boundingClientRect((res) => { 189 | if (res && res.length) { 190 | this.setData({ 191 | loader: res[0] 192 | }) 193 | } 194 | }).exec() 195 | } 196 | }) -------------------------------------------------------------------------------- /lib/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true 3 | } -------------------------------------------------------------------------------- /lib/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{text}} 9 | 更新于:{{time}} 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /lib/index.wxss: -------------------------------------------------------------------------------- 1 | .scroll-view .new-block { 2 | width: 100%; 3 | height: 100px; 4 | background-color: #333; 5 | } 6 | .scroll-view .new-block:nth-child(even) { 7 | background-color: #fff; 8 | } 9 | .scroll-view .refresh-loader { 10 | width: 100%; 11 | height: 100%; 12 | } 13 | @keyframes rotate { 14 | 0% { 15 | -webkit-transform: rotate(0deg) scale(1); 16 | transform: rotate(0deg) scale(1); 17 | } 18 | 50% { 19 | -webkit-transform: rotate(180deg) scale(1); 20 | transform: rotate(180deg) scale(1); 21 | } 22 | 100% { 23 | -webkit-transform: rotate(360deg) scale(1); 24 | transform: rotate(360deg) scale(1); 25 | } 26 | } 27 | @-webkit-keyframes rotate { 28 | 0% { 29 | -webkit-transform: rotate(0deg) scale(1); 30 | transform: rotate(0deg) scale(1); 31 | } 32 | 50% { 33 | -webkit-transform: rotate(180deg) scale(1); 34 | transform: rotate(180deg) scale(1); 35 | } 36 | 100% { 37 | -webkit-transform: rotate(360deg) scale(1); 38 | transform: rotate(360deg) scale(1); 39 | } 40 | } 41 | .scroll-view .refresh-content { 42 | position: relative; 43 | -webkit-overflow-scrolling: touch; 44 | -webkit-transform: translate3d(0, 0, 0); 45 | transform: translate3d(0, 0, 0); 46 | } 47 | .scroll-view .refresh-load { 48 | background-color: #f5f5f5; 49 | width: 100%; 50 | position: absolute; 51 | top: 0; 52 | left: 0; 53 | padding: 8px 0; 54 | display: flex; 55 | flex-direction: row; 56 | justify-content: center; 57 | align-items: center; 58 | } 59 | .scroll-view .refresh-load .refresh-load__content { 60 | width: 260px; 61 | display: flex; 62 | flex-direction: row; 63 | justify-content: flex-start; 64 | align-items: center; 65 | } 66 | .scroll-view .refresh-load .refresh-load__content .refresh-load__text > view { 67 | font-size: 14px; 68 | display: flex; 69 | flex-direction: row; 70 | justify-content: center; 71 | } 72 | .scroll-view .refresh-pull-arrow { 73 | width: 24px; 74 | height: 24px; 75 | visibility: visible; 76 | margin-right: 20px; 77 | background: no-repeat center; 78 | background-image: url('data:image/svg+xml;charset=utf-8,'); 79 | background-size: cover; 80 | z-index: 10; 81 | -webkit-transform: rotate(0deg) translate3d(0, 0, 0); 82 | transform: rotate(0deg) translate3d(0, 0, 0); 83 | -webkit-transition-duration: 300ms; 84 | transition-duration: 300ms; 85 | } 86 | .scroll-view .refresh-pull-down { 87 | -webkit-transform: rotate(0deg) translate3d(0, 0, 0); 88 | transform: rotate(0deg) translate3d(0, 0, 0); 89 | } 90 | .scroll-view .refresh-pull-up { 91 | -webkit-transform: rotate(180deg) translate3d(0, 0, 0); 92 | transform: rotate(180deg) translate3d(0, 0, 0); 93 | } 94 | .scroll-view .refreshing { 95 | width: 24px; 96 | height: 24px; 97 | background-image: url('data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iciIgd2lkdGg9JzEyMHB4JyBoZWlnaHQ9JzEyMHB4JyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj4KICAgIDxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIiBmaWxsPSJub25lIiBjbGFzcz0iYmsiPjwvcmVjdD4KICAgIDxyZWN0IHg9JzQ2LjUnIHk9JzQwJyB3aWR0aD0nNycgaGVpZ2h0PScyMCcgcng9JzUnIHJ5PSc1JyBmaWxsPScjRTlFOUU5JwogICAgICAgICAgdHJhbnNmb3JtPSdyb3RhdGUoMCA1MCA1MCkgdHJhbnNsYXRlKDAgLTMwKSc+CiAgICA8L3JlY3Q+CiAgICA8cmVjdCB4PSc0Ni41JyB5PSc0MCcgd2lkdGg9JzcnIGhlaWdodD0nMjAnIHJ4PSc1JyByeT0nNScgZmlsbD0nIzk4OTY5NycKICAgICAgICAgIHRyYW5zZm9ybT0ncm90YXRlKDMwIDUwIDUwKSB0cmFuc2xhdGUoMCAtMzApJz4KICAgICAgICAgICAgICAgICByZXBlYXRDb3VudD0naW5kZWZpbml0ZScvPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyM5Qjk5OUEnCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSg2MCA1MCA1MCkgdHJhbnNsYXRlKDAgLTMwKSc+CiAgICAgICAgICAgICAgICAgcmVwZWF0Q291bnQ9J2luZGVmaW5pdGUnLz4KICAgIDwvcmVjdD4KICAgIDxyZWN0IHg9JzQ2LjUnIHk9JzQwJyB3aWR0aD0nNycgaGVpZ2h0PScyMCcgcng9JzUnIHJ5PSc1JyBmaWxsPScjQTNBMUEyJwogICAgICAgICAgdHJhbnNmb3JtPSdyb3RhdGUoOTAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyNBQkE5QUEnCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSgxMjAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyNCMkIyQjInCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSgxNTAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyNCQUI4QjknCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSgxODAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyNDMkMwQzEnCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSgyMTAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyNDQkNCQ0InCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSgyNDAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyNEMkQyRDInCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSgyNzAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyNEQURBREEnCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSgzMDAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyNFMkUyRTInCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSgzMzAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0Pgo8L3N2Zz4='); 98 | -webkit-animation: rotate 1s 0s linear infinite; 99 | animation: rotate 1s 0s linear infinite; 100 | } 101 | .scroll-view .loading-load { 102 | width: 100%; 103 | position: relative; 104 | padding: 8px 0; 105 | } 106 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "weapp-scroll-view-refresh", 3 | "version": "1.0.1", 4 | "description": "wechat weapp scroll-view refresh component", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Chaunjie/weapp-scroll-view-refresh.git" 12 | }, 13 | "author": "chaunjie", 14 | "license": "ISC", 15 | "miniprogram": "lib", 16 | "devDependencies": {}, 17 | "bugs": { 18 | "url": "https://github.com/Chaunjie/weapp-scroll-view-refresh/issues" 19 | }, 20 | "homepage": "https://github.com/Chaunjie/weapp-scroll-view-refresh#readme" 21 | } 22 | -------------------------------------------------------------------------------- /screenshot/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chaunjie/weapp-scroll-view-refresh/c0413b810227c7e71651e73ff53cd5e8ae9f69a2/screenshot/1.gif --------------------------------------------------------------------------------