mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
407 lines
9.7 KiB
Vue
407 lines
9.7 KiB
Vue
<template>
|
|
<div
|
|
ref="root"
|
|
:class="carouselClasses"
|
|
@mouseenter.stop="handleMouseEnter"
|
|
@mouseleave.stop="handleMouseLeave"
|
|
>
|
|
<div class="el-carousel__container" :style="{ height: height }">
|
|
<transition v-if="arrowDisplay" name="carousel-arrow-left">
|
|
<button
|
|
v-show="
|
|
(arrow === 'always' || data.hover) &&
|
|
(props.loop || data.activeIndex > 0)
|
|
"
|
|
type="button"
|
|
class="el-carousel__arrow el-carousel__arrow--left"
|
|
@mouseenter="handleButtonEnter('left')"
|
|
@mouseleave="handleButtonLeave"
|
|
@click.stop="throttledArrowClick(data.activeIndex - 1)"
|
|
>
|
|
<i class="el-icon-arrow-left"></i>
|
|
</button>
|
|
</transition>
|
|
<transition v-if="arrowDisplay" name="carousel-arrow-right">
|
|
<button
|
|
v-show="
|
|
(arrow === 'always' || data.hover) &&
|
|
(props.loop || data.activeIndex < items.length - 1)
|
|
"
|
|
type="button"
|
|
class="el-carousel__arrow el-carousel__arrow--right"
|
|
@mouseenter="handleButtonEnter('right')"
|
|
@mouseleave="handleButtonLeave"
|
|
@click.stop="throttledArrowClick(data.activeIndex + 1)"
|
|
>
|
|
<i class="el-icon-arrow-right"></i>
|
|
</button>
|
|
</transition>
|
|
<slot></slot>
|
|
</div>
|
|
<ul v-if="indicatorPosition !== 'none'" :class="indicatorsClasses">
|
|
<li
|
|
v-for="(item, index) in items"
|
|
:key="index"
|
|
:class="[
|
|
'el-carousel__indicator',
|
|
'el-carousel__indicator--' + direction,
|
|
{ 'is-active': index === data.activeIndex },
|
|
]"
|
|
@mouseenter="throttledIndicatorHover(index)"
|
|
@click.stop="handleIndicatorClick(index)"
|
|
>
|
|
<button class="el-carousel__button">
|
|
<span v-if="hasLabel">{{ item.label }}</span>
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import {
|
|
defineComponent,
|
|
reactive,
|
|
computed,
|
|
ref,
|
|
provide,
|
|
onMounted,
|
|
onBeforeUnmount,
|
|
watch,
|
|
nextTick,
|
|
} from 'vue'
|
|
import throttle from 'lodash/throttle'
|
|
import {
|
|
addResizeListener,
|
|
removeResizeListener,
|
|
} from '@element-plus/utils/resize-event'
|
|
import { ICarouselProps, CarouselItem, InjectCarouselScope } from './carousel'
|
|
|
|
export default defineComponent({
|
|
name: 'ElCarousel',
|
|
props: {
|
|
initialIndex: {
|
|
type: Number,
|
|
default: 0,
|
|
},
|
|
height: { type: String, default: '' },
|
|
trigger: {
|
|
type: String,
|
|
default: 'hover',
|
|
},
|
|
autoplay: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
interval: {
|
|
type: Number,
|
|
default: 3000,
|
|
},
|
|
indicatorPosition: { type: String, default: '' },
|
|
indicator: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
arrow: {
|
|
type: String,
|
|
default: 'hover',
|
|
},
|
|
type: { type: String, default: '' },
|
|
loop: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
direction: {
|
|
type: String,
|
|
default: 'horizontal',
|
|
validator(val: string) {
|
|
return ['horizontal', 'vertical'].includes(val)
|
|
},
|
|
},
|
|
pauseOnHover: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
},
|
|
emits: ['change'],
|
|
setup(props: ICarouselProps, { emit }) {
|
|
// data
|
|
const data = reactive<{
|
|
activeIndex: number
|
|
containerWidth: number
|
|
timer: null | ReturnType<typeof setInterval>
|
|
hover: boolean
|
|
}>({
|
|
activeIndex: -1,
|
|
containerWidth: 0,
|
|
timer: null,
|
|
hover: false,
|
|
})
|
|
|
|
// refs
|
|
const root = ref(null)
|
|
const items = ref<CarouselItem[]>([])
|
|
const offsetWidth = ref(0)
|
|
const offsetHeight = ref(0)
|
|
|
|
// computed
|
|
const arrowDisplay = computed(
|
|
() => props.arrow !== 'never' && props.direction !== 'vertical',
|
|
)
|
|
|
|
const hasLabel = computed(() => {
|
|
return items.value.some(item => item.label.toString().length > 0)
|
|
})
|
|
|
|
const carouselClasses = computed(() => {
|
|
const classes = ['el-carousel', 'el-carousel--' + props.direction]
|
|
if (props.type === 'card') {
|
|
classes.push('el-carousel--card')
|
|
}
|
|
return classes
|
|
})
|
|
|
|
const indicatorsClasses = computed(() => {
|
|
const classes = [
|
|
'el-carousel__indicators',
|
|
'el-carousel__indicators--' + props.direction,
|
|
]
|
|
if (hasLabel.value) {
|
|
classes.push('el-carousel__indicators--labels')
|
|
}
|
|
if (props.indicatorPosition === 'outside' || props.type === 'card') {
|
|
classes.push('el-carousel__indicators--outside')
|
|
}
|
|
return classes
|
|
})
|
|
|
|
// methods
|
|
const throttledArrowClick = throttle(
|
|
index => {
|
|
setActiveItem(index)
|
|
},
|
|
300,
|
|
{ trailing: true },
|
|
)
|
|
|
|
const throttledIndicatorHover = throttle(index => {
|
|
handleIndicatorHover(index)
|
|
}, 300)
|
|
|
|
function pauseTimer() {
|
|
if (data.timer) {
|
|
clearInterval(data.timer)
|
|
data.timer = null
|
|
}
|
|
}
|
|
|
|
function startTimer() {
|
|
if (props.interval <= 0 || !props.autoplay || data.timer) return
|
|
data.timer = setInterval(() => playSlides(), props.interval)
|
|
}
|
|
|
|
const playSlides = () => {
|
|
if (data.activeIndex < items.value.length - 1) {
|
|
data.activeIndex = data.activeIndex + 1
|
|
} else if (props.loop) {
|
|
data.activeIndex = 0
|
|
}
|
|
}
|
|
|
|
function setActiveItem(index) {
|
|
if (typeof index === 'string') {
|
|
const filteredItems = items.value.filter(item => item.name === index)
|
|
if (filteredItems.length > 0) {
|
|
index = items.value.indexOf(filteredItems[0])
|
|
}
|
|
}
|
|
index = Number(index)
|
|
if (isNaN(index) || index !== Math.floor(index)) {
|
|
console.warn('[Element Warn][Carousel]index must be an integer.')
|
|
return
|
|
}
|
|
let length = items.value.length
|
|
const oldIndex = data.activeIndex
|
|
if (index < 0) {
|
|
data.activeIndex = props.loop ? length - 1 : 0
|
|
} else if (index >= length) {
|
|
data.activeIndex = props.loop ? 0 : length - 1
|
|
} else {
|
|
data.activeIndex = index
|
|
}
|
|
if (oldIndex === data.activeIndex) {
|
|
resetItemPosition(oldIndex)
|
|
}
|
|
}
|
|
|
|
function resetItemPosition(oldIndex) {
|
|
items.value.forEach((item, index) => {
|
|
item.translateItem(index, data.activeIndex, oldIndex)
|
|
})
|
|
}
|
|
|
|
function addItem(item) {
|
|
items.value.push(item)
|
|
}
|
|
|
|
function removeItem(uid) {
|
|
const index = items.value.findIndex(item => item.uid === uid)
|
|
if (index !== -1) {
|
|
items.value.splice(index, 1)
|
|
if(data.activeIndex === index) next()
|
|
}
|
|
}
|
|
|
|
function itemInStage(item, index) {
|
|
const length = items.value.length
|
|
if (
|
|
(index === length - 1 && item.inStage && items.value[0].active) ||
|
|
(item.inStage &&
|
|
items.value[index + 1] &&
|
|
items.value[index + 1].active)
|
|
) {
|
|
return 'left'
|
|
} else if (
|
|
(index === 0 && item.inStage && items.value[length - 1].active) ||
|
|
(item.inStage &&
|
|
items.value[index - 1] &&
|
|
items.value[index - 1].active)
|
|
) {
|
|
return 'right'
|
|
}
|
|
return false
|
|
}
|
|
|
|
function handleMouseEnter() {
|
|
data.hover = true
|
|
if (props.pauseOnHover) {
|
|
pauseTimer()
|
|
}
|
|
}
|
|
|
|
function handleMouseLeave() {
|
|
data.hover = false
|
|
startTimer()
|
|
}
|
|
|
|
function handleButtonEnter(arrow) {
|
|
if (props.direction === 'vertical') return
|
|
items.value.forEach((item, index) => {
|
|
if (arrow === itemInStage(item, index)) {
|
|
item.hover = true
|
|
}
|
|
})
|
|
}
|
|
|
|
function handleButtonLeave() {
|
|
if (props.direction === 'vertical') return
|
|
items.value.forEach(item => {
|
|
item.hover = false
|
|
})
|
|
}
|
|
|
|
function handleIndicatorClick(index) {
|
|
data.activeIndex = index
|
|
}
|
|
|
|
function handleIndicatorHover(index) {
|
|
if (props.trigger === 'hover' && index !== data.activeIndex) {
|
|
data.activeIndex = index
|
|
}
|
|
}
|
|
|
|
function prev() {
|
|
setActiveItem(data.activeIndex - 1)
|
|
}
|
|
|
|
function next() {
|
|
setActiveItem(data.activeIndex + 1)
|
|
}
|
|
|
|
// watch
|
|
watch(
|
|
() => data.activeIndex,
|
|
(current, prev) => {
|
|
resetItemPosition(prev)
|
|
if (prev > -1) {
|
|
emit('change', current, prev)
|
|
}
|
|
},
|
|
)
|
|
watch(
|
|
() => props.autoplay,
|
|
current => {
|
|
current ? startTimer() : pauseTimer()
|
|
},
|
|
)
|
|
watch(
|
|
() => props.loop,
|
|
() => {
|
|
setActiveItem(data.activeIndex)
|
|
},
|
|
)
|
|
|
|
// lifecycle
|
|
onMounted(() => {
|
|
nextTick(() => {
|
|
addResizeListener(root.value, resetItemPosition)
|
|
if (root.value) {
|
|
offsetWidth.value = root.value.offsetWidth
|
|
offsetHeight.value = root.value.offsetHeight
|
|
}
|
|
if (
|
|
props.initialIndex < items.value.length &&
|
|
props.initialIndex >= 0
|
|
) {
|
|
data.activeIndex = props.initialIndex
|
|
}
|
|
startTimer()
|
|
})
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
if (root.value) removeResizeListener(root.value, resetItemPosition)
|
|
pauseTimer()
|
|
})
|
|
|
|
// provide
|
|
provide<InjectCarouselScope>('injectCarouselScope', {
|
|
direction: props.direction,
|
|
offsetWidth,
|
|
offsetHeight,
|
|
type: props.type,
|
|
items,
|
|
loop: props.loop,
|
|
addItem,
|
|
removeItem,
|
|
setActiveItem,
|
|
})
|
|
|
|
return {
|
|
data,
|
|
props,
|
|
items,
|
|
|
|
arrowDisplay,
|
|
carouselClasses,
|
|
indicatorsClasses,
|
|
hasLabel,
|
|
|
|
handleMouseEnter,
|
|
handleMouseLeave,
|
|
handleIndicatorClick,
|
|
throttledArrowClick,
|
|
throttledIndicatorHover,
|
|
handleButtonEnter,
|
|
handleButtonLeave,
|
|
|
|
prev,
|
|
next,
|
|
setActiveItem,
|
|
|
|
root,
|
|
}
|
|
},
|
|
})
|
|
</script>
|