I. Component introduction
Website links: Carousel components | Element (gitee. IO).
The Carousel component, also known as a Carousel diagram, is used to display banners in a Carousel on the home page of a website.
Carousel needs to work with the Carousel-item component.
1.1 attributes
1.1.1 according to class
- Height: String, set the height of the lantern;
- Type: String type, a merry-go-round type, can be set to card;
- Indicator position: String, setting the position of the indicator. Outside or None;
- Direction: The value is a string. The optional value is
horizontal/vertical
Horizontal by default;
1.1.2 control class
- Initial-index: indicates the number type. The value is the index of the image displayed during the initial state.
- Trigger: String type; Set the trigger mode of the indicator, default is hover, can be set to click;
- Autoplay: Boolean indicates whether to switch automatically. The default value is True.
- Interval: indicates the number type. The unit is ms. The default value is 3000 (3s).
- Arrow: The arrow type is string. The arrow display time can be set to
always/hover/never
The default is hover; - Loop: Boolean type, which sets the loop display. Default is true.
- Pause-on-hover: Boolean type, pause switching when hover, default is true;
1.2 event
- “Change” : triggered when the picture is switched
Current active Index, previously active Index
1.3 Callable APIS
- SetActiveItem: Toggle slide; Parameter: slide index or
carousel-item
The name attribute value of; - Prev: Switch to the previous slide;
- Next: Switch to the next slide;
Second, source code analysis
2.1 Carousel components
2.1.1 the template
<template>
<div
ref="root"
:class="carouselClasses"// Mouse in/out event, used to pause/restart the rowhead timer @mouseenter.stop="handleMouseEnter"
@mouseleave.stop="handleMouseLeave"
>
<div class="el-carousel__container" :style="{ height: height }">// Switch arrows left and right<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"// Throttling @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>/ / indicator<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>
Copy the code
2.1.2 script
// Part of the core source
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)
// Store CarouselItem subcomponent data
const items = ref<CarouselItem[]>([])
// Calculate the attribute that controls whether the left and right arrow arrow is displayed or not: arrow is set to never and diretcion is vertical
const arrowDisplay = computed(
() = >props.arrow ! = ='never'&& props.direction ! = ='vertical'.)// Calculate whether the carousel-item has the lable attribute
const hasLabel = computed(() = > {
return items.value.some(item= > item.label.toString().length > 0)})// Calculate the class of the attribute, carousel
const carouselClasses = computed(() = > {
const classes = ['el-carousel'.'el-carousel--' + props.direction]
if (props.type === 'card') {
classes.push('el-carousel--card')}return classes
})
// Calculate the class of the attribute indicator
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
// The carousel-item registration method is executed in the Carousel-item component using the provide/inject mode
function addItem(item) {
items.value.push(item)
}
// Carousel-item unload method
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()
}
}
// Throttling processing, to avoid the slide in the rotation, while the user click left and right arrow switch caused by flashing
const throttledArrowClick = throttle(
index= > {
setActiveItem(index)
},
300,
{ trailing: true},)// Throttling processing, indicator hover event toggle
const throttledIndicatorHover = throttle(index= > {
handleIndicatorHover(index)
}, 300)
// Clear the timer
function pauseTimer() {
if (data.timer) {
clearInterval(data.timer)
data.timer = null}}// Start timer
function startTimer() {
if (props.interval <= 0| |! props.autoplay || data.timer)return
// Execute playSides in the timer callback
data.timer = setInterval(() = > playSlides(), props.interval)
}
/ / set activeIndex
const playSlides = () = > {
if (data.activeIndex < items.value.length - 1) {
data.activeIndex = data.activeIndex + 1
} else if (props.loop) {
// Loop
data.activeIndex = 0}}// Sets the activation object, which can be called internally by the component or by the consumer through the REF
function setActiveItem(index) {
// The string parameter is the name attribute of the carousel-item that the user can pass in when calling the API
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) {
// Reset the slide position
resetItemPosition(oldIndex)
}
}
// Set the location of the carouse item
function resetItemPosition(oldIndex) {
items.value.forEach((item, index) = > {
// translateItem is the method of the Carousel-item subcomponent to set the offset position of the slide
item.translateItem(index, data.activeIndex, oldIndex)
})
}
// Instage indicates whether the slide is currently playing in card mode
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
}
// Move the mouse to pause the timer
function handleMouseEnter() {
data.hover = true
if (props.pauseOnHover) {
pauseTimer()
}
}
// Move the mouse away to start the timer
function handleMouseLeave() {
data.hover = false
startTimer()
}
// The mouse enters the left-right toggle arrow
function handleButtonEnter(arrow) {
if (props.direction === 'vertical') return
items.value.forEach((item, index) = > {
if (arrow === itemInStage(item, index)) {
item.hover = true}})}// Mouse away left and right toggle arrow
function handleButtonLeave() {
if (props.direction === 'vertical') return
items.value.forEach(item= > {
item.hover = false})}// set activeIndex to the index of the click
function handleIndicatorClick(index) {
data.activeIndex = index
}
// indicator Hover event. If trigger is set to Hover, activeIndex is set to the index of the click
function handleIndicatorHover(index) {
if (props.trigger === 'hover'&& index ! == data.activeIndex) { data.activeIndex = index } }// Switch to the previous method
function prev() {
setActiveItem(data.activeIndex - 1)}// Switch to the next method
function next() {
setActiveItem(data.activeIndex + 1)}// watch
// monitor the change of activeIndex,
watch(
() = > data.activeIndex,
(current, prev) = > {
resetItemPosition(prev)
if (prev > -1) {
// Emit the change event
emit('change', current, prev)
}
},
)
// Monitor autoPlay changes and start/pause the timer
watch(
() = > props.autoplay,
current= > {
current ? startTimer() : pauseTimer()
},
)
// Monitor loop changes,
watch(
() = > props.loop,
() = > {
setActiveItem(data.activeIndex)
},
)
// lifecycle
onMounted(() = > {
nextTick(() = > {
// Listen for the resize event
addResizeListener(root.value, resetItemPosition)
// initialIndex Set activeIndex to initialIndex when the range is reasonable
if (
props.initialIndex < items.value.length &&
props.initialIndex >= 0
) {
data.activeIndex = props.initialIndex
}
// Start timer
startTimer()
})
})
onBeforeUnmount(() = > {
// Clear event listener during uninstallation
if (root.value) removeResizeListener(root.value, resetItemPosition)
pauseTimer()
})
// provide, provide data to the child component
provide<InjectCarouselScope>('injectCarouselScope', {
root,
direction: props.direction,
type: props.type,
items,
loop: props.loop,
addItem,
removeItem,
setActiveItem,
})
},
Copy the code
2.2 Carousel – item component
2.2.1 the template
<template>
<div
v-show="data.ready"
class="el-carousel__item"
:class="{ 'is-active': data.active, 'el-carousel__item--card': type === 'card', 'is-in-stage': data.inStage, 'is-hover': data.hover, 'is-animating': data.animating, }"
:style="itemStyle"
@click="handleItemClick"
>
<div
v-if="type === 'card'"
v-show=! "" data.active"
class="el-carousel__mask"
></div>
<slot></slot>
</div>
</template>
Copy the code
2.2.2 script
setup(props: ICarouselItemProps) {
// Get the component instance
const instance = getCurrentInstance()
// data
const data = reactive({
hover: false.translate: 0.scale: 1.active: false.ready: false.inStage: false.animating: false,})// inject data passed by the parent component
const injectCarouselScope: InjectCarouselScope = inject(
'injectCarouselScope'.)// computed
// Direction of the parent component
const parentDirection = computed(() = > {
return injectCarouselScope.direction
})
// Dynamic inline style,
const itemStyle = computed(() = > {
const translateType =
parentDirection.value === 'vertical' ? 'translateY' : 'translateX'
const value = `${translateType}(${data.translate}px) scale(${data.scale}) `
// eg:style="transform: translateX(673px) scale(1);"
const style: PartialCSSStyleDeclaration = {
transform: value,
}
// Autoprefixer is a tool method for adding browser compatible prefixes such as MS -,webkit-, etc
return autoprefixer(style)
})
// methods
// In the case of looping, calculate a reasonable index based on the index passed in, the length of the currently active index and items
function processIndex(index, activeIndex, length) {
if (activeIndex === 0 && index === length - 1) {
return -1
} else if (activeIndex === length - 1 && index === 0) {
return length
} else if (index < activeIndex - 1 && activeIndex - index >= length / 2) {
return length + 1
} else if (index > activeIndex + 1 && index - activeIndex >= length / 2) {
return -2
}
return index
}
// Calculate the displacement distance in card mode
function calcCardTranslate(index, activeIndex) {
constparentWidth = injectCarouselScope.root.value? .offsetWidth ||0
// inStage refers to the current slide in card mode
if (data.inStage) {
return (
(parentWidth * ((2 - CARD_SCALE) * (index - activeIndex) + 1)) / 4)}else if (index < activeIndex) {
return(- (1 + CARD_SCALE) * parentWidth) / 4
} else {
return ((3 + CARD_SCALE) * parentWidth) / 4}}// calculate displacement distance: displacement distance = width/height of parent container * (index and activeIndex difference)
function calcTranslate(index, activeIndex, isVertical) {
constdistance = (isVertical ? injectCarouselScope.root.value? .offsetHeight : injectCarouselScope.root.value? .offsetWidth) ||0
return distance * (index - activeIndex)
}
const translateItem = (
index: number,
activeIndex: number,
oldIndex: number.) = > {
const parentType = injectCarouselScope.type
const length = injectCarouselScope.items.value.length
// Add animating properties to the current slide and the previous slide
if(parentType ! = ='card'&& oldIndex ! = =undefined) {
data.animating = index === activeIndex || index === oldIndex
}
// In the loop case, processIndex is called to calculate the actual Index
if(index ! == activeIndex && length >2 && injectCarouselScope.loop) {
index = processIndex(index, activeIndex, length)
}
/ / card mode
if (parentType === 'card') {
// The card mode does not support vertical form
if (parentDirection.value === 'vertical') {
console.warn(
'[Element Warn][Carousel]vertical direction is not supported in card mode',
)
}
data.inStage = Math.round(Math.abs(index - activeIndex)) <= 1
data.active = index === activeIndex
// Call calcCardTranslate to calculate the offset distance
data.translate = calcCardTranslate(index, activeIndex)
data.scale = data.active ? 1 : CARD_SCALE
} else {
// Normal mode
data.active = index === activeIndex
const isVertical = parentDirection.value === 'vertical'
// Calculate the offset distance
data.translate = calcTranslate(index, activeIndex, isVertical)
}
data.ready = true
}
// Click the slide
function handleItemClick() {
if (injectCarouselScope && injectCarouselScope.type === 'card') {
const index = injectCarouselScope.items.value
.map(d= > d.uid)
.indexOf(instance.uid)
// Call the setActiveItem method of the parent component
injectCarouselScope.setActiveItem(index)
}
}
// lifecycle
onMounted(() = > {
// Register by calling the parent's addItem method
if (injectCarouselScope.addItem) {
injectCarouselScope.addItem({
uid: instance.uid, ... props, ... toRefs(data), translateItem, }) } }) onUnmounted(() = > {
// Cancel registration
if (injectCarouselScope.removeItem) {
injectCarouselScope.removeItem(instance.uid)
}
})
return {
data,
itemStyle,
translateItem,
type: injectCarouselScope.type,
handleItemClick,
}
},
}
Copy the code
2.3 summarize
- Provide/Inject Provides data interaction between parent and child components.
- The parent component is responsible for maintaining the activeIndex, providing the setActiveItem method to set the active slide;
- The child component provides the translateItem method to calculate the offset based on its own index and activeIndex, including the width/height of the parent component;
- The principle of the lantern style is: all slides are tiled/vertically displayed, and the offset distance of each slide is adjusted by translateX/Y