Skip to content

Commit a4c4bb4

Browse files
committed
添加carousel组件
1 parent 0f84cdf commit a4c4bb4

File tree

6 files changed

+509
-0
lines changed

6 files changed

+509
-0
lines changed

packages/carousel-item/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { App } from 'vue'
2+
import Component from '../carousel/src/item.vue'
3+
export const install = function (app: App) {
4+
app.component(Component.name as string, Component)
5+
}
6+
7+
export default Component

packages/carousel/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { App } from 'vue'
2+
import Component from './src/index.vue'
3+
export const install = function (app: App) {
4+
app.component(Component.name as string, Component)
5+
}
6+
7+
export default Component

packages/carousel/src/index.vue

+329
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
<template>
2+
<div
3+
:class="carouselClasses"
4+
@mouseenter.stop="handleMouseEnter"
5+
@mouseleave.stop="handleMouseLeave">
6+
<div
7+
class="el-carousel__container"
8+
:style="{ height: height }">
9+
<transition
10+
v-if="arrowDisplay"
11+
name="carousel-arrow-left">
12+
<button
13+
type="button"
14+
v-show="(arrow === 'always' || hover) && (loop || activeIndex > 0)"
15+
@mouseenter="handleButtonEnter('left')"
16+
@mouseleave="handleButtonLeave"
17+
@click.stop="throttledArrowClick(activeIndex - 1)"
18+
class="el-carousel__arrow el-carousel__arrow--left">
19+
<i class="el-icon-arrow-left"></i>
20+
</button>
21+
</transition>
22+
<transition
23+
v-if="arrowDisplay"
24+
name="carousel-arrow-right">
25+
<button
26+
type="button"
27+
v-show="(arrow === 'always' || hover) && (loop || activeIndex < items.length - 1)"
28+
@mouseenter="handleButtonEnter('right')"
29+
@mouseleave="handleButtonLeave"
30+
@click.stop="throttledArrowClick(activeIndex + 1)"
31+
class="el-carousel__arrow el-carousel__arrow--right">
32+
<i class="el-icon-arrow-right"></i>
33+
</button>
34+
</transition>
35+
<slot></slot>
36+
</div>
37+
<ul
38+
v-if="indicatorPosition !== 'none'"
39+
:class="indicatorsClasses">
40+
<li
41+
v-for="(item, index) in items"
42+
:key="index"
43+
:class="[
44+
'el-carousel__indicator',
45+
'el-carousel__indicator--' + direction,
46+
{ 'is-active': index === activeIndex }]"
47+
@mouseenter="throttledIndicatorHover(index)"
48+
@click.stop="handleIndicatorClick(index)">
49+
<button class="el-carousel__button">
50+
<span v-if="hasLabel">{{ item.label }}</span>
51+
</button>
52+
</li>
53+
</ul>
54+
</div>
55+
</template>
56+
57+
<script lang="tsx">
58+
// @ts-ignore
59+
import { throttle } from 'throttle-debounce'
60+
// @ts-ignore
61+
import { addResizeListener, removeResizeListener } from '@/utils/resize-event'
62+
import { ComponentInternalInstance, computed, defineComponent, getCurrentInstance, h, nextTick, onBeforeUnmount, onMounted, Prop, provide, reactive, ref, toRef, toRefs, watch } from 'vue'
63+
import { Carousel } from './type'
64+
export default defineComponent({
65+
name: 'ElCarousel',
66+
67+
props: {
68+
initialIndex: {
69+
type: Number,
70+
default: 0
71+
},
72+
height: String,
73+
trigger: {
74+
type: String,
75+
default: 'hover'
76+
},
77+
autoplay: {
78+
type: Boolean,
79+
default: true
80+
},
81+
interval: {
82+
type: Number,
83+
default: 3000
84+
},
85+
indicatorPosition: String,
86+
indicator: {
87+
type: Boolean,
88+
default: true
89+
},
90+
arrow: {
91+
type: String,
92+
default: 'hover'
93+
},
94+
type: String,
95+
loop: {
96+
type: Boolean,
97+
default: true
98+
},
99+
direction: {
100+
type: String,
101+
default: 'horizontal',
102+
validator(val: string) {
103+
return ['horizontal', 'vertical'].indexOf(val) !== -1
104+
}
105+
} as Prop<string>
106+
},
107+
108+
setup(props, { emit, slots }) {
109+
const state = reactive({
110+
activeIndex: -1,
111+
containerWidth: 0,
112+
timer: 0,
113+
hover: false
114+
})
115+
const items = ref([] as anyObject[])
116+
117+
const arrowDisplay = computed(() => {
118+
return props.arrow !== 'never' && props.direction !== 'vertical'
119+
})
120+
121+
const hasLabel = computed(() => {
122+
return items.value.some(item => item.props.label.toString().length > 0)
123+
})
124+
125+
const carouselClasses = computed(() => {
126+
const classes = ['el-carousel', 'el-carousel--' + props.direction]
127+
if (props.type === 'card') {
128+
classes.push('el-carousel--card')
129+
}
130+
return classes
131+
})
132+
133+
const indicatorsClasses = computed(() => {
134+
const classes = ['el-carousel__indicators', 'el-carousel__indicators--' + props.direction]
135+
if (hasLabel.value) {
136+
classes.push('el-carousel__indicators--labels')
137+
}
138+
if (props.indicatorPosition === 'outside' || props.type === 'card') {
139+
classes.push('el-carousel__indicators--outside')
140+
}
141+
return classes
142+
})
143+
144+
const handleMouseEnter = () => {
145+
state.hover = true
146+
pauseTimer()
147+
}
148+
149+
const handleMouseLeave = () => {
150+
state.hover = false
151+
startTimer()
152+
}
153+
154+
const itemInStage = (item: any, index: number) => {
155+
const length = items.value.length
156+
if (index === length - 1 && item.inStage && items.value[0].active ||
157+
(item.inStage && items.value[index + 1] && items.value[index + 1].active)) {
158+
return 'left'
159+
} else if (index === 0 && item.inStage && items.value[length - 1].active ||
160+
(item.inStage && items.value[index - 1] && items.value[index - 1].active)) {
161+
return 'right'
162+
}
163+
return false
164+
}
165+
166+
const handleButtonEnter = (arrow: boolean | string) => {
167+
if (props.direction === 'vertical') return
168+
items.value.forEach((item, index) => {
169+
if (arrow === itemInStage(item, index)) {
170+
item.hover = true
171+
}
172+
})
173+
}
174+
175+
const handleButtonLeave = () => {
176+
if (props.direction === 'vertical') return
177+
items.value.forEach(item => {
178+
item.hover = false
179+
})
180+
}
181+
182+
const children = ref([] as ComponentInternalInstance[])
183+
const mountChildren = (instance: ComponentInternalInstance) => {
184+
children.value.push(instance)
185+
}
186+
const updateItems = () => {
187+
items.value = children.value.filter(child => child.type.name === 'ElCarouselItem')
188+
}
189+
190+
const resetItemPosition = (oldIndex: number) => {
191+
items.value.forEach((item, index) => {
192+
item.setupState.translateItem(index, state.activeIndex, oldIndex)
193+
})
194+
}
195+
196+
const playSlides = () => {
197+
if (state.activeIndex < items.value.length - 1) {
198+
state.activeIndex++
199+
} else if (props.loop) {
200+
state.activeIndex = 0
201+
}
202+
}
203+
204+
const pauseTimer = () => {
205+
if (state.timer) {
206+
clearInterval(state.timer)
207+
state.timer = 0
208+
}
209+
}
210+
211+
const startTimer = () => {
212+
if (props.interval <= 0 || !props.autoplay || state.timer) return
213+
state.timer = setInterval(playSlides, props.interval)
214+
}
215+
216+
const setActiveItem = (index: number) => {
217+
if (typeof index === 'string') {
218+
const filteredItems = items.value.filter(item => item.name === index)
219+
if (filteredItems.length > 0) {
220+
index = items.value.indexOf(filteredItems[0])
221+
}
222+
}
223+
index = Number(index)
224+
if (isNaN(index) || index !== Math.floor(index)) {
225+
console.warn('[Element Warn][Carousel]index must be an integer.')
226+
return
227+
}
228+
let length = items.value.length
229+
const oldIndex = state.activeIndex
230+
if (index < 0) {
231+
state.activeIndex = props.loop ? length - 1 : 0
232+
} else if (index >= length) {
233+
state.activeIndex = props.loop ? 0 : length - 1
234+
} else {
235+
state.activeIndex = index
236+
}
237+
if (oldIndex === state.activeIndex) {
238+
resetItemPosition(oldIndex)
239+
}
240+
}
241+
242+
const prev = () => {
243+
setActiveItem(state.activeIndex - 1)
244+
}
245+
246+
const next = () => {
247+
setActiveItem(state.activeIndex + 1)
248+
}
249+
250+
const handleIndicatorClick = (index: number) => {
251+
state.activeIndex = index
252+
}
253+
254+
const handleIndicatorHover = (index: number) => {
255+
if (props.trigger === 'hover' && index !== state.activeIndex) {
256+
state.activeIndex = index
257+
}
258+
}
259+
const instance = getCurrentInstance() as ComponentInternalInstance
260+
const throttledArrowClick = throttle(300, true, (index: number) => {
261+
setActiveItem(index)
262+
})
263+
const throttledIndicatorHover = throttle(300, (index: number) => {
264+
handleIndicatorHover(index)
265+
})
266+
watch(() => items.value, (val)=> {
267+
if (val.length > 0) setActiveItem(props.initialIndex)
268+
})
269+
watch(() => state.activeIndex, (val, oldVal)=> {
270+
resetItemPosition(oldVal)
271+
if (oldVal > -1) {
272+
emit('change', val, oldVal)
273+
}
274+
})
275+
watch(() => props.autoplay, (val)=> {
276+
val ? startTimer() : pauseTimer()
277+
})
278+
watch(() => props.loop, ()=> {
279+
setActiveItem(state.activeIndex)
280+
})
281+
provide('carousel', {
282+
items,
283+
updateItems,
284+
setActiveItem,
285+
type: props.type,
286+
loop: props.loop,
287+
direction: props.direction,
288+
mountChildren
289+
} as Carousel)
290+
onMounted(() => {
291+
updateItems()
292+
nextTick(() => {
293+
addResizeListener(instance.vnode.el, resetItemPosition)
294+
if (props.initialIndex < items.value.length && props.initialIndex >= 0) {
295+
state.activeIndex = props.initialIndex
296+
}
297+
startTimer()
298+
})
299+
})
300+
onBeforeUnmount(() => {
301+
if (instance.vnode.el) removeResizeListener(instance.vnode.el, resetItemPosition)
302+
pauseTimer()
303+
})
304+
return {
305+
...toRefs(state),
306+
items,
307+
arrowDisplay,
308+
hasLabel,
309+
carouselClasses,
310+
indicatorsClasses,
311+
handleMouseEnter,
312+
handleMouseLeave,
313+
itemInStage,
314+
handleButtonEnter,
315+
handleButtonLeave,
316+
prev,
317+
next,
318+
handleIndicatorClick,
319+
handleIndicatorHover,
320+
throttledArrowClick,
321+
throttledIndicatorHover
322+
}
323+
}
324+
})
325+
</script>
326+
327+
<style lang="scss">
328+
@import 'theme/carousel.scss';
329+
</style>

0 commit comments

Comments
 (0)