предисловие
Давно затянувшийся выпадающий список для обновления компонента Vue — наконец-то он здесь, на самом деле, он давно написан, и я давно не писал статьи…
Над изображениями
технический пункт
На самом деле технический момент не сложный, в основном с использованием сенсорного события H5:
- touchstart: событие, вызванное касанием экрана пальцем, основная задача — получить координату Y щелчка мыши при его срабатывании, event.touches[0].pageY.
- touchmove: событие, вызванное скольжением пальца, основная задача состоит в том, чтобы получить координату перемещения Y за вычетом начальной координаты Y, когда оно запускается, чтобы получить расстояние перемещения, а затем использовать преобразование для изменения положения контейнера.
- touchend: событие, вызванное отпусканием пальца, основная задача — отпустить мышь, чтобы восстановить div в исходное положение.
Идеальное использование
Поскольку это компонент, разработчикам должно быть легко им пользоваться, просто и легко.
Предположим, что наши компоненты обновления по запросу и загрузки по запросуupLoadDownRefresh:
<upLoadDownRefresh>
<div v-for="(item, index) in list" :key="index">
{{ item.name }}
</div>
</upLoadDownRefresh>
Просто поместите компонент в легкое, чтобы получить эффект.
Реализация: Версия 1. Реализовать контейнер, который можно перетаскивать и возвращать в исходное положение после отпускания пальца.
<template>
<div>
<div id="scroll-container"
@touchstart.stop="handlerTouchStart"
@touchmove.stop="handlerTouchMove"
@touchend.stop="handlerTouchEnd"
ref="scrollContainer"
>
<slot></slot>
</div>
</div>
</template>
<script>
export default {
data () {
return {
startLocation: '', // 记录鼠标点击的位置
moveDistance: 0, // 记录移动的位置
distance: '' // 记录移动的距离
}
},
methods: {
// 获取手指触屏时的屏幕Y轴位置
handlerTouchStart (e) {
this.startLocation = e.touches[0].pageY
},
// 获取手指移动的距离
handlerTouchMove (e) {
this.moveDistance = Math.floor(e.touches[0].pageY - this.startLocation)
this.$refs.scrollContainer.style.transform = `translateY(${this.moveDistance}px)`
},
// 获取手指松开的Y轴位置
handlerTouchEnd (e) {
// 清除已移动的距离
this.moveDistance = 0
// 恢复原位
this.$refs.scrollContainer.style.transform = 'translateY(0px)'
}
}
}
</script>
<style scoped>
#scroll-container {
background-color: yellow;
}
</style>
В основном с помощью события touchstart, чтобы получить расстояние по оси Y движения контейнера, затем вычислить расстояние по оси Y движения пальца в событии touchmove и переместить, установив transform: translateY(), и, наконец, восстановить исходное положение в преобразование события касания: translateY( 0px).
Задержка восстановления рендеринга
Из приведенных выше рендеров видно, что процесс отпускания пальца для восстановления положения восстанавливается мгновенно, и эффект не является дружественным для пользователя, далее мы его улучшим. добавлениемtransitionРеализуйте отложенное восстановление.
Давай, по коду:
<template>
<div>
<div id="scroll-container"
...
:class="{'transition': isTransition}"
>
<slot></slot>
</div>
</div>
</template>
<script>
export default {
data () {
return {
...
isTransition: false // 是否启动transition
...
}
},
methods: {
// 获取手指触屏时的屏幕Y轴位置
handlerTouchStart (e) {
...
this.isTransition = false
...
},
// 获取手指松开的Y轴位置
handlerTouchEnd (e) {
...
this.isTransition = true // 开启transition
...
}
}
}
</script>
<style scoped>
...
.transition {
transition: all .7s;
}
...
</style>
Задержка восстановления может быть достигнута путем добавления вышеуказанного кода, в основном с помощьюtransitionЭтот стиль css реализован и переданthis.isTransitionчтобы определить нужно ли активировать стиль, ведь стиль только при отпускании пальца, т.е.touchend事件добавляется, когдаtouchstart事件закрытие.
Рендеринг версии 2:
Drag and drop - эффект восстановления отсутствует, выглядит хорошо, но еще много чего не хватает.Например, отсутствует анимация загрузки анимации при потягивании вниз для обновления и подтягивании для загрузки, что крайне плохо для пользователя опыт. , теперь добавим эти две анимации.
Версия 2: добавление анимации загрузки и обновления
Изменить часть кода:
<template>
<div>
<div id="scroll-container"
@touchstart.stop="handlerTouchStart"
@touchmove.stop="handlerTouchMove"
@touchend.stop="handlerTouchEnd"
ref="scrollContainer"
:class="{'transition': isTransition}"
>
<!-- 添加刷新图片 -->
<div class="refresh">
<img
src="https://www.easyicon.net/api/resizeApi.php?id=1190769&size=48"
>
</div>
<slot></slot>
<!-- 添加加载图片 -->
<div class="load">
<img src="https://img.lanrentuku.com/img/allimg/1212/5-121204193R5-50.gif">
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
startLocation: '', // 记录鼠标点击的位置
moveDistance: 0, // 记录移动的位置
distance: '', // 记录移动的距离
isTransition: false // 是否启动transition
}
},
methods: {
// 获取手指触屏时的屏幕Y轴位置
handlerTouchStart (e) {
this.isTransition = false
this.startLocation = e.touches[0].pageY
},
// 获取手指移动的距离
handlerTouchMove (e) {
this.moveDistance = Math.floor(e.touches[0].pageY - this.startLocation)
this.$refs.scrollContainer.style.transform = `translateY(${this.moveDistance}px)`
},
// 获取手指松开的Y轴位置
handlerTouchEnd (e) {
this.moveDistance = 0 // 清除已移动的距离
this.isTransition = true // 开启transition
this.$refs.scrollContainer.style.transform = 'translateY(0px)'
}
}
}
</script>
<style scoped>
#scroll-container {
background-color: yellow;
}
.transition {
transition: all .7s;
}
/* -----添加新样式------ */
.load, .refresh {
text-align: center;
}
.load img, .refresh img {
width: 20px;
}
/* -----添加新样式------ */
</style>
Изображение эффекта:
В настоящее время箭头,加载статичны, а сейчас есть такой спрос, пусть выше箭头Стрелка указывает вниз, когда я тяну вниз и когда я отпускаю палец箭头направлен вверх, при возвращении в исходное положение,箭头стали加载. а также下拉刷新а также上拉加载Срабатывает при перетаскивании на определенное расстояние.
Модификация HTML-кода:
<template>
<div>
<div id="scroll-container"
@touchstart.stop="handlerTouchStart"
@touchmove.stop="handlerTouchMove"
@touchend.stop="handlerTouchEnd"
ref="scrollContainer"
:class="{'transition': isTransition}"
>
<!-- 根据isDisplay.refresh 动态隐藏显示 -->
<div :class="['refresh', {'display': isDisplay.refresh}]">
<!-- 添加isShrnked 加载 和 箭头相互转换 -->
<!-- 添加rotate类 反转箭头 下箭头和上箭头互相转换 -->
<img
:src="isShrinked? loadImg : refreshImg"
:class="{'rotate': isRotate}"
>
</div>
<slot></slot>
<!-- 根据isDisplay.load 动态隐藏显示 -->
<div :class="['load', {display: isDisplay.load}]">
<img :src="loadImg">
</div>
</div>
</div>
</template>
Стрелка ↑ и стрелка ↓ преобразуются с помощью transform: rotate(180deg), здесь адреса загрузки картинок и обновления картинок хранятся в переменных для облегчения динамического переключения.
Модификация кода JS:
<script>
// 拖拽状态 true:下拉 false:上拉
let SCROLLSTATUS
export default {
props: {
// 能够拖拽的最大距离
maxDistance: {
style: Number,
default: 300
},
// 定义触发加载刷新事件的拉伸长度
triggerDistance: {
style: Number,
default: 100
}
},
data () {
return {
startLocation: '', // 记录鼠标点击的位置
moveDistance: 0, // 记录移动的位置
distance: '', // 记录移动的距离
isTransition: false, // 是否启动transition
isDisplay: {
refresh: true,
load: true
},
// 把图片地址抽离出来 方便动态切换
loadImg: 'https://img.lanrentuku.com/img/allimg/1212/5-121204193R5-50.gif',
refreshImg: 'https://www.easyicon.net/api/resizeApi.php?id=1190769&size=48',
isRotate: false, // 是否选择箭头
isShrinked: false // 是否收缩完成
}
},
methods: {
// 获取手指触屏时的屏幕Y轴位置
handlerTouchStart (e) {
this.isTransition = false
this.startLocation = e.touches[0].pageY
// 重置箭头反转
this.isRotate = false
// 重置箭头
this.isShrinked = false
},
// 获取手指移动的距离
handlerTouchMove (e) {
if (this.moveDistance > this.maxDistance + 1) {
this.isRotate = true
return
}
this.moveDistance = Math.floor(e.touches[0].pageY - this.startLocation)
this.$refs.scrollContainer.style.transform = `translateY(${this.moveDistance}px)`
// 显示加载 刷新图片
if (this.moveDistance > this.triggerDistance && this.isDisplay.refresh) {
this.isDisplay.refresh = false
} else if (this.moveDistance < -this.triggerDistance && this.isDisplay.load) {
this.isDisplay.load = false
}
},
// 获取手指松开的Y轴位置
handlerTouchEnd (e) {
// 记录拖拽状态是为上拉还是下拉
SCROLLSTATUS = this.moveDistance > 0
this.isTransition = true // 开启transition
this.$refs.scrollContainer.style.transform = 'translateY(0px)'
if (Math.abs(this.moveDistance) < this.triggerDistance) return (this.moveDistance = 0)
this.moveDistance = 0 // 清除已移动的距离
// 拖拽距离是否大于指定的触发长度
// 容器位置恢复后触发
setTimeout(() => {
this.shrinked()
}, 700)
},
// 容器恢复后的操作
shrinked () {
if (SCROLLSTATUS) {
// 下拉刷新业务逻辑
// 已经恢复完,箭头转为加载
this.isShrinked = true
// 隐藏刷新、加载
this.isDisplay.refresh = true
this.isDisplay.load = true
alert('这是下拉操作')
} else {
// 上拉加载业务逻辑
alert('这是上拉操作')
}
}
}
}
</script>
Объем информации модифицированный этим кодом большой, читать нужно внимательно... Добавим два свойства PROPS:maxDistanceа такжеtriggerDistance.
- maxDistance: эта переменная представляет собой максимальное расстояние, которое можно перетащить.
- triggerDistance: эта переменная представляет собой расстояние, которое запускает загрузку и обновление.
визуализация
На данный момент остается только последний шаг, который заключается в передаче событий обновления и загрузки во внешние компоненты.Когда нам нужно создавать события?
я определяюprops.triggerDistanceАтрибут, обновление и загрузка будут запускаться только тогда, когда расстояние перетаскивания больше этого значения, поэтому мы находимся в@touchmoveСобытие выдается в событии для использования внешними компонентами, но оно будет срабатывать до тех пор, пока перемещается мышь.@touchmoveСобытия не всегда могут запускаться непрерывноVue.$emit()Да, это слишком сильно влияет на производительность.
Решение состоит в том, чтобы использовать массив для хранения обновления и загрузкиметод emit()`.
Версия 3 Добавление событий обновления и загрузки
Изменить JS:
<script>
// 拖拽状态 true:下拉 false:上拉
let SCROLLSTATUS
export default {
props: {
...
},
data () {
return {
...
// 添加emit缓存数组,并以undefined填充
emitEvents: new Array(2).fill(undefined)
...
}
},
methods: {
// 获取手指移动的距离
handlerTouchMove (e) {
if (this.moveDistance > this.maxDistance + 1) {
this.isRotate = true
return
}
this.moveDistance = Math.floor(e.touches[0].pageY - this.startLocation)
this.$refs.scrollContainer.style.transform = `translateY(${this.moveDistance}px)`
// 显示加载 刷新图片
if (this.moveDistance > this.triggerDistance && this.isDisplay.refresh) {
this.isDisplay.refresh = false
} else if (this.moveDistance < -this.triggerDistance && this.isDisplay.load) {
this.isDisplay.load = false
}
// 缓存刷新的emit
if (this.moveDistance > this.triggerDistance && !this.emitEvents[0]) {
this.emitEvents[0] = function () { this.$emit('refresh', this.displayDiv) }
}
// 缓存加载的emit
if (this.moveDistance < -this.triggerDistance && !this.emitEvents[1]) {
this.emitEvents[1] = function () { this.$emit('load', this.displayDiv) }
}
},
// 获取手指松开的Y轴位置
handlerTouchEnd (e) {
// 记录拖拽状态是为上拉还是下拉
SCROLLSTATUS = this.moveDistance > 0
this.isTransition = true // 开启transition
this.$refs.scrollContainer.style.transform = 'translateY(0px)'
if (Math.abs(this.moveDistance) < this.triggerDistance) return (this.moveDistance = 0)
this.moveDistance = 0 // 清除已移动的距离
// 拖拽距离是否大于指定的触发长度
// 容器位置恢复后触发
setTimeout(() => {
this.shrinked()
}, 700)
// 遍历emit并执行
this.emitEvents.forEach((fn, index) => {
if (!fn) return
this.emitEvents[index] = undefined
fn.apply(this)
})
},
// 容器恢复后的操作
shrinked () {
if (SCROLLSTATUS) {
// 下拉恢复完,箭头转为加载
this.isShrinked = true
} else {
// 上拉回复完
}
},
// 该方法通过$emit()传给外部组件调用 然后隐藏刷新、加载的gif图片
displayDiv () {
this.isDisplay.refresh = true
this.isDisplay.load = true
}
}
}
</script>
Код здесь в основном проверяет длину перетаскивания, чтобы длина была больше, чемthis.triggerDistanceЗатем выполняются операции обновления и загрузки, и каждый раз, когда он проверяется, он будет оцениваться.this.emitEventsМассив уже существуетemit()метод, пропустить, если он существует, сохранить, если он не существует, и, наконец, в@touchendобход центраthis.emitEvents. с вызовом$emitПри вызове метода метод скрытия gif передается внешнему компоненту для вызова.
Компоненты используют:
<template>
<div>
<refreshLoad
@refresh="refresh"
@load="load"
:maxDistance="300"
:triggerDistance="100"
>
<div v-for="(item, index) in list" :key="index">
{{ item.name }}
</div>
</refreshLoad>
</div>
</template>
<script>
import refreshLoad from './refreshLoading'
export default {
data () {
return {
timer: '',
list: [
{
name: '张三'
},
{
name: '李四'
},
{
name: '王五'
},
{
name: '赵六'
}
],
date: '',
show: false
}
},
components: {
refreshLoad
},
methods: {
refresh (done) {
console.log(done)
if (this.timer) clearTimeout(this.timer)
this.timer = setTimeout(() => {
console.log('refresh')
done()
}, 2000)
},
load (done) {
if (this.timer) clearTimeout(this.timer)
this.timer = setTimeout(() => {
done()
this.list = this.list.concat([{
name: '新增的' + Math.ceil(Math.random() * 10)
}, {
name: '新增的' + Math.ceil(Math.random() * 10)
}, {
name: '新增的' + Math.ceil(Math.random() * 10)
}])
}, 1000)
}
}
}
</script>
Здесь важно отметить, что@refreshа также@loadМетод принимает параметрыdone,здесьdoneв компонентеdisplayDivметод, пользователь скрывает загруженное и обновленное изображение gif.
окончательные визуализации
быть улучшенным
Если нет адаптации мобильного терминала, вам придется перейти к размеру пикселя в компоненте в соответствии с вашим личным проектом разработки.
В нем нет разделяющих функций вытягивания и подтягивания, они смешаны, и заинтересованные друзья могут изменить их самостоятельно.
Загружено в нпм
Я загрузил модифицированный компонент в npm, который можно установить и использовать напрямую.
- Установить:
npm i -s refresh-load-plugin
- использовать:
import upLoadDownRefresh from 'refresh-load-plugin'
import 'refresh-load-plugin/lib/refresh-load-plugin.css'
Vue.use(upLoadDownRefresh)
<template>
<upLoadDownRefresh
@refresh="refresh"
@load="load"
:maxDistance="300"
:triggerDistance="100"
>
<div v-for="(item, index) in list" :key="index">
{{ item.name }}
</div>
</upLoadDownRefresh>
</template>