├── .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 | 
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
--------------------------------------------------------------------------------