git
Научитесь немного находить точку.
git
найдено вышеscroll-tab-barЯ написал это после того, как добавил свое собственное мышление к компонентам.
Требование: реализовать свайп влево и вправо для переключения страниц.
окончательные визуализации
Функциональный анализ:
- Щелкните верхнее меню, чтобы перейти на соответствующую страницу.
- Нажмите на экран и проведите пальцем влево или вправо для переключения страниц.
- В первый раз будет загружаться только содержимое домашней страницы.
- Другие страницы, только после входа, будут загружены.
Подумайте, анализ:
- Нужен компонент меню, нажмите, чтобы изменить
tabIndex
(какая страница отображается) - нужен один
ScrollTab
Компонент, управляет событием нажатия пальца, управляет отображениемtabIndex
- нужен один
ScrollTabCol
компонент, обертывающий реальныйPage
компонент для управления загрузкой.
результат:
-
ScrollTab
- принимает один параметр (
tabIndex
tabIndex
- Логика реализации события нажатия пальцем
- принимает один параметр (
-
ScrollTabCol
Оборачивает каждую страницу и принимает параметрload
Чтобы контролировать, загружена ли текущая страница.
Процесс реализации:
Главная страница не имеет значения, просто вставьте код напрямую.
<script setup>
import { ref } from 'vue'
import ScrollTab from './components/ScrollTab.vue'
import ScrollTabCol from './components/ScrollTabCol.vue'
import Page from './components/Page.vue'
let tabIndex = ref(0) // todo 控制显示哪个页面
let loadIndex = ref(0) // todo 控制加载哪个页面
// todo 通知页面切换
const selectChange = (value) => {
tabIndex.value = value
loadIndex.value = value
}
</script>
<template>
<!-- // todo 顶部菜单 -->
<ul class="label-list">
<li
class="label-list-item"
:class="{ 'select-label-list-item': tabIndex === 0 }"
@click="tabIndex = 0"
>1</li>
<li
class="label-list-item"
:class="{ 'select-label-list-item': tabIndex === 1 }"
@click="tabIndex = 1"
>2</li>
<li
class="label-list-item"
:class="{ 'select-label-list-item': tabIndex === 2 }"
@click="tabIndex = 2"
>3</li>
<li
class="label-list-item"
:class="{ 'select-label-list-item': tabIndex === 3 }"
@click="tabIndex = 3"
>4</li>
<li
class="label-list-item"
:class="{ 'select-label-list-item': tabIndex === 4 }"
@click="tabIndex = 4"
>5</li>
</ul>
<!-- // todo 页面 -->
<ScrollTab :tabIndex="tabIndex" @selectChange="selectChange">
<ScrollTabCol class="item" :loading="loadIndex === 0">
<Page msg="1"></Page>
</ScrollTabCol>
<ScrollTabCol class="item" :loading="loadIndex === 1">
<Page msg="2"></Page>
</ScrollTabCol>
<ScrollTabCol class="item" :loading="loadIndex === 2">
<Page msg="3"></Page>
</ScrollTabCol>
<ScrollTabCol class="item" :loading="loadIndex === 3">
<Page msg="4"></Page>
</ScrollTabCol>
<ScrollTabCol class="item" :loading="loadIndex === 4">
<Page msg="5"></Page>
</ScrollTabCol>
</ScrollTab>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
html,
body,
div,
body,
ul,
li {
margin: 0;
padding: 0;
list-style: none;
text-decoration: none;
}
.label-list {
position: fixed;
width: 100%;
z-index: 100;
display: flex;
height: 50px;
line-height: 50px;
}
.label-list-item {
flex-grow: 1;
background: #909399;
color: #fff;
text-align: center;
}
.select-label-list-item {
font-weight: 600;
background: #67c23a;
}
.item {
text-align: center;
background: #409eff;
line-height: 400px;
color: #fff;
font-size: 100px;
font-weight: 600;
}
</style>
ScrollTab
Реализация компонента
Подумайте, анализ:
-
Получает параметр и управляет событием, очевидно
props
а такжеemit
. -
Чтобы добиться эффекта скольжения влево и вправо, легко придумать карусель.
-
Три события нажатия пальцем:
touchstart
,touchmove
,touchend
.
Приблизительный логический поток
- согласно с
子组件
Количество и ширина экрана, сначала реализовать горизонтальную «карусель» - Время реализации нажатия пальцем рассчитывается в соответствии с положением мыши при нажатии пальца и положением мыши при поднятии пальца: двигаться влево, двигаться вправо и не двигаться.
- переключать корреспонденцию
tabIndex
мероприятие
Шаблоны и CSS
<template>
<div class="scroll-tab">
<div
class="scroll-tab-item"
:style="{ width: scrollWidth + 'px', transform: `translate(${contentBoxLeft}px, 0)`, transitionDuration: delay + 's' }"
ref="scrollTabItem"
@touchstart="handleTouchStart($event)"
@touchmove="handleTouchMove($event)"
@touchend="handleTouchEnd($event)"
>
<slot></slot>
</div>
</div>
</template>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.scroll-tab {
width: 100%;
overflow: hidden;
}
.scroll-tab-item {
display: flex;
flex-wrap: nowrap;
align-items: flex-start;
}
</style>
логика
Первый шаг: Нажмите на меню и переключитесь на соответствующую страницу при переключении.
const props = defineProps({
tabIndex: Number,
})
const emit = defineEmits(['selectChange'])
watch(props, () => {
contentBoxLeft.value = calcluateBoxLeft(props.tabIndex)
tabIndex.value = props.tabIndex
})
Шаг 2: Рассчитайте необходимую ширину карусели в соответствии с количеством подкомпонентов и ширины экранаscrollWidth
onMounted
Жизненный цикл.
clientWidth
а такжеclientHeight
Будет использоваться в нескольких файлах, все извлечены.
export const clientWidth = document.documentElement.clientWidth ? document.documentElement.clientWidth : document.body.clientWidth
export const clientHeight = document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight
onMounted(() => {
calculateScrollWidth()
})
// ? 计算 scrollWidth
const calculateScrollWidth = () => {
children = scrollTabItem.value.children
length.value = children.length
scrollWidth.value = children.length * clientWidth
}
Третий шаг: реализовать событие нажатия тремя пальцами
Логический поток:
touchstart
:
Статус записи: 1. Нажат ли палец, 2. Положение пальца нажато
touchmove
:
Рассчитайте, является ли направление скольжения вверх и вниз или влево и вправо через положение пальца при нажатии и текущее положение пальца.
Если это слева и справа, пройти速率
Установите последнюю позицию карусели.
touchend
:
Рассчитывается на основе последней позиции и начальной позиции пальца移动的距离
.
согласно移动的距离
а такжеtabIndex
, чтобы определить, следует ли переключать страницы.
/**
* @param tabIndex 当前显示的 page
* @param contentBoxLeft 当前轮播的位置
* @param clientWidth 页面的宽度
* @param length 页面的数量
* @param scrollWidth 轮播图总的宽度
*/
const useMoveTouch = (tabIndex, contentBoxLeft, clientWidth, length, scrollWidth) => {
let moveOptions = reactive({
direction: '', // ? 方向
isStart: false, // ? 是否按下
startPos: null, // ? 记录开始的位置
endPos: null // ? 记录结束的位置
})
const calcluateBoxLeft = (index) => {
window.scrollTo(0, 0)
return -(clientWidth * index)
}
const handleTouchStart = (e) => {
moveOptions.isStart = true
moveOptions.startPos = e.touches[0]
moveOptions.endPos = e.touches[0]
}
const handleTouchMove = (e) => {
// ? 根据当前 e的位置,判断出是向 上下 还是 左右滑动
let left = e.touches[0].clientX - moveOptions.endPos.clientX
let top = e.touches[0].clientY - moveOptions.endPos.clientY
if (moveOptions.isStart) {
moveOptions.direction = (Math.abs(left) > Math.abs(top)) ? 'X' : 'Y'
}
moveOptions.endPos = e.touches[0]
if (moveOptions.direction === 'X') {
e.preventDefault()
// todo 左右移动
// ? 1. 定义移动的 速率
let coefficient = 1
if (contentBoxLeft.value >= 20 || contentBoxLeft.value < -(scrollWidth.value - clientWidth + 20)) {
coefficient = 0
} else if (left >= 0 && tabIndex.value === 0) {
coefficient = 0.3
} else if (left <= 0 && tabIndex.value === length.value - 1) {
coefficient = 0.3
}
contentBoxLeft.value += coefficient * left
}
}
const handleTouchEnd = (e) => {
if (moveOptions.direction !== 'X') return
// ? 1. 获取移动的距离
const moveLen = moveOptions.endPos.clientX - moveOptions.startPos.clientX
if (moveLen > 80 && tabIndex.value !== 0) {
tabIndex.value--
} else if (moveLen < -80 && tabIndex.value !== length.value - 1) {
tabIndex.value++
}
// ? 计算出偏移量
contentBoxLeft.value = calcluateBoxLeft(tabIndex.value)
}
return {
calcluateBoxLeft,
handleTouchStart,
handleTouchMove,
handleTouchEnd
}
}
Шаг 4: Уведомить родителя, если требуется коммутатор.
const usetabIndex = (emit) => {
let tabIndex = ref(0)
watch(tabIndex, () => {
emit('selectChange', tabIndex.value)
})
return tabIndex
}
ScrollTabCol
Реализация компонента
Подумайте, анализ:
выполнить:
<template>
<div class="scroll-tab-col" :style="{width:clientWidth + 'px', height: clientHeight + 'px'}">
<!-- // todo 空白页 -->
<div v-if="!show">进入的时候是空白效果</div>
<!-- // todo 具体的页面 -->
<slot v-else></slot>
</div>
</template>
<script setup>
import {clientWidth, clientHeight} from './clientParameters'
import {defineProps, watchEffect, ref} from 'vue'
const props = defineProps({
loading: Boolean
})
// todo 控制显示 空白页,还是挂载
const show = ref(false)
// todo 根据 props.loading 控制挂载
const stop = watchEffect(() => {
if (props.loading) {
show.value = true
setTimeout(() => {
stop()
})
}
})
</script>
<style scoped>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
</style>
Резюме мышления
Первая скорость загрузки панели, управление нагрузкой, затем, в первый раз при рендеринге только что сделает содержимое домашней страницы, и без содержимого всех страниц загружаются и отображаются.
зачем использоватьwatchEffect
без использованияwatch
?
watch
Да, но
Когда я это писал, я думал, что это событие прослушивания нужно только слушатьprops.loading=true
, после этого прослушивание не требуется, и его нужно выполнить немедленно.
watch
а такжеwatchEffect
Небольшая разницаwatchEffect立即执行
хочу использоватьwatch
, логика как ниже
const stop = watch(props, () => {
if (props.loading) {
show.value = true
setTimeout(() => {
stop()
})
}
},{
immediate: true
})