| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- <template>
- <div class="indicator-home">
- <div class="notice" :style="noticeStyle"><span>下拉刷新内容</span></div>
- <div ref="toolbar" class="toolbar" :style="toolbarStyle">
- <Icon
- icon="tabler:menu-deep"
- class="search"
- style="transform: rotateY(180deg)"
- @click="$emit('showSlidebar')"
- />
- <div ref="tabCtn" class="tab-ctn">
- <div ref="tabs" class="tabs">
- <div
- v-for="(label, i) in tabLabels"
- :key="label"
- class="tab"
- :class="{ active: index === i }"
- @click.stop="change(i)"
- >
- <span>{{ label }}</span>
- </div>
- </div>
- <div ref="indicator" class="indicator" />
- </div>
- <Icon icon="ion:search" class="search" @click="_no" />
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { Icon } from '@iconify/vue'
- import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
- import bus from '@/utils/bus'
- import { _css } from '@/utils/dom'
- import { _no } from '@/utils/index'
- const props = defineProps({
- loading: { type: Boolean, default: false },
- name: { type: String, default: '' },
- index: { type: Number, default: 0 },
- })
- const emit = defineEmits<{ 'update:index': [number]; showSlidebar: [] }>()
- const tabLabels = ['我的', '推荐']
- const tabCtn = ref<HTMLElement | null>(null)
- const tabs = ref<HTMLElement | null>(null)
- const indicator = ref<HTMLElement | null>(null)
- const lefts = ref<number[]>([])
- const indicatorSpace = ref(0)
- const moveY = ref(0)
- const judgeValue = 20
- const homeRefresh = 60
- const transform = computed(
- () =>
- `translate3d(0, ${moveY.value - judgeValue > homeRefresh ? homeRefresh : moveY.value - judgeValue}px, 0)`,
- )
- const toolbarStyle = computed(() => {
- if (props.loading) {
- return { opacity: 1, transitionDuration: '300ms', transform: 'translate3d(0, 0, 0)' }
- }
- if (moveY.value) {
- return {
- opacity: 1 - (moveY.value - judgeValue) / (homeRefresh / 2),
- transform: transform.value,
- }
- }
- return { opacity: 1, transitionDuration: '300ms', transform: 'translate3d(0, 0, 0)' }
- })
- const noticeStyle = computed(() => {
- if (props.loading) return { opacity: 0 }
- if (moveY.value) {
- return {
- opacity: (moveY.value - judgeValue) / (homeRefresh / 2) - 0.5,
- transform: transform.value,
- }
- }
- return { opacity: 0 }
- })
- function updateIndicatorLeft(index: number, animate = true) {
- if (!indicator.value || !lefts.value.length) return
- const safeIndex = Math.max(0, Math.min(index, lefts.value.length - 1))
- _css(indicator.value, 'transition-duration', animate ? '300ms' : '0ms')
- _css(indicator.value, 'left', lefts.value[safeIndex] + 'px')
- }
- function change(index: number) {
- emit('update:index', index)
- updateIndicatorLeft(index)
- }
- function initTabs() {
- if (!tabs.value || !indicator.value || !tabCtn.value) return
- const indicatorWidth = _css(indicator.value, 'width') as number
- const tabCtnLeft = tabCtn.value.getBoundingClientRect().left
- const positions: number[] = []
- for (let i = 0; i < tabs.value.children.length; i++) {
- const item = tabs.value.children[i] as HTMLElement
- const rect = item.getBoundingClientRect()
- positions.push(rect.left - tabCtnLeft + (rect.width * 0.5 - indicatorWidth / 2))
- }
- lefts.value = positions
- indicatorSpace.value = positions.length > 1 ? positions[1] - positions[0] : 0
- updateIndicatorLeft(props.index, false)
- }
- function move(val?: unknown) {
- const e = val as number
- if (!indicator.value || !indicatorSpace.value) return
- const safeIndex = Math.max(0, Math.min(props.index, lefts.value.length - 1))
- _css(indicator.value, 'transition-duration', '0ms')
- _css(
- indicator.value,
- 'left',
- lefts.value[safeIndex] - e / (window.innerWidth / indicatorSpace.value) + 'px',
- )
- }
- function end(val?: unknown) {
- const index = val as number
- moveY.value = 0
- updateIndicatorLeft(index)
- setTimeout(() => {
- if (indicator.value) _css(indicator.value, 'transition-duration', '0ms')
- }, 300)
- }
- watch(
- () => props.index,
- (index) => {
- nextTick(() => updateIndicatorLeft(index))
- },
- )
- onMounted(() => {
- nextTick(() => {
- initTabs()
- requestAnimationFrame(initTabs)
- })
- bus.on(props.name + '-moveX', move)
- bus.on(props.name + '-moveY', (e) => {
- moveY.value = e as number
- })
- bus.on(props.name + '-end', end)
- })
- onUnmounted(() => {
- bus.off(props.name + '-moveX', move)
- bus.off(props.name + '-moveY')
- bus.off(props.name + '-end', end)
- })
- </script>
- <style scoped lang="less">
- .indicator-home {
- position: absolute;
- font-size: 16rem;
- top: 0;
- left: 0;
- z-index: 2;
- width: 100%;
- color: white;
- height: var(--home-header-height);
- font-weight: bold;
- .notice {
- opacity: 0;
- top: 0;
- position: absolute;
- width: 100vw;
- height: 100%;
- display: flex;
- justify-content: center;
- align-items: center;
- }
- .toolbar {
- z-index: 2;
- position: relative;
- width: 100%;
- height: 100%;
- box-sizing: border-box;
- padding: 0 15rem;
- display: flex;
- justify-content: center;
- align-items: center;
- .tab-ctn {
- position: relative;
- display: flex;
- justify-content: center;
- .tabs {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 40rem;
- .tab {
- transition: color 0.3s;
- color: rgba(255, 255, 255, 0.7);
- font-size: 17rem;
- cursor: pointer;
- white-space: nowrap;
- &.active {
- color: white;
- }
- }
- }
- .indicator {
- position: absolute;
- bottom: -6rem;
- height: 2.6rem;
- width: 26rem;
- background: #fff;
- border-radius: 5rem;
- }
- }
- .search {
- position: absolute;
- top: 0;
- bottom: 0;
- margin: auto 0;
- height: 24rem;
- display: flex;
- align-items: center;
- color: white;
- font-size: 24rem;
- cursor: pointer;
- &:first-of-type {
- left: 15rem;
- }
- &:last-of-type {
- right: 15rem;
- }
- }
- }
- }
- </style>
|