mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
feat(components): [Carousel] Add motion blur functionality (#15603)
* feat(components): [Carousel] Add motion blur functionality docs(components): [Carousel] Add example properties docs(components): [Carousel] Add docs properties * fix(components): [Carousel] change the id name in filter in svg fix(components): [Carousel] button to remove motion blur effect fix(components): [Carousel] svg hides when motion blur is false * update(components): [Carousel] docs/en-US/component/carousel.md Co-authored-by: kooriookami <38392315+kooriookami@users.noreply.github.com> * update(components): [Carousel] docs/examples/carousel/motion-blur.vue Co-authored-by: kooriookami <38392315+kooriookami@users.noreply.github.com> * update(components): [Carousel] docs/examples/carousel/motion-blur.vue Co-authored-by: kooriookami <38392315+kooriookami@users.noreply.github.com> * update(components): [Carousel] packages/components/carousel/src/carousel.ts Co-authored-by: kooriookami <38392315+kooriookami@users.noreply.github.com> * docs(components): [Carousel] add feature version labels --------- Co-authored-by: kooriookami <38392315+kooriookami@users.noreply.github.com>
This commit is contained in:
@@ -15,6 +15,16 @@ carousel/basic
|
||||
|
||||
:::
|
||||
|
||||
## Motion blur ^(2.6.0)
|
||||
|
||||
Add motion blur to infuse dynamism and smoothness into the carousel.
|
||||
|
||||
:::demo Enabling motion blur enhances the dynamism and smoothness of the carousel. By default, the `motion-blur` parameter is set to `false`, activating this feature and providing a visually engaging experience.
|
||||
|
||||
carousel/motion-blur
|
||||
|
||||
:::
|
||||
|
||||
## Indicators
|
||||
|
||||
Indicators can be displayed outside the carousel
|
||||
@@ -67,19 +77,20 @@ carousel/vertical
|
||||
|
||||
## Carousel Attributes
|
||||
|
||||
| Name | Description | Type | Accepted Values | Default |
|
||||
| ------------------ | ----------------------------------------------------- | ------- | ------------------- | ---------- |
|
||||
| height | height of the carousel | string | — | — |
|
||||
| initial-index | index of the initially active slide (starting from 0) | number | — | 0 |
|
||||
| trigger | how indicators are triggered | string | hover/click | hover |
|
||||
| autoplay | whether automatically loop the slides | boolean | — | true |
|
||||
| interval | interval of the auto loop, in milliseconds | number | — | 3000 |
|
||||
| indicator-position | position of the indicators | string | outside/none | — |
|
||||
| arrow | when arrows are shown | string | always/hover/never | hover |
|
||||
| type | type of the Carousel | string | card | — |
|
||||
| loop | display the items in loop | boolean | - | true |
|
||||
| direction | display direction | string | horizontal/vertical | horizontal |
|
||||
| pause-on-hover | pause autoplay when hover | boolean | - | true |
|
||||
| Name | Description | Type | Accepted Values | Default |
|
||||
| ---------------------- | ----------------------------------------------------- | ------- | ------------------- | ---------- |
|
||||
| height | height of the carousel | string | — | — |
|
||||
| initial-index | index of the initially active slide (starting from 0) | number | — | 0 |
|
||||
| trigger | how indicators are triggered | string | hover/click | hover |
|
||||
| autoplay | whether automatically loop the slides | boolean | — | true |
|
||||
| interval | interval of the auto loop, in milliseconds | number | — | 3000 |
|
||||
| indicator-position | position of the indicators | string | outside/none | — |
|
||||
| arrow | when arrows are shown | string | always/hover/never | hover |
|
||||
| type | type of the Carousel | string | card | — |
|
||||
| loop | display the items in loop | boolean | - | true |
|
||||
| direction | display direction | string | horizontal/vertical | horizontal |
|
||||
| pause-on-hover | pause autoplay when hover | boolean | - | true |
|
||||
| motion-blur ^(2.6.0) | infuse dynamism and smoothness into the carousel | boolean | - | false |
|
||||
|
||||
## Carousel Events
|
||||
|
||||
|
||||
43
docs/examples/carousel/motion-blur.vue
Normal file
43
docs/examples/carousel/motion-blur.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div class="block text-center">
|
||||
<span class="demonstration">Motion blur the switch (default)</span>
|
||||
<el-carousel height="200px" motion-blur>
|
||||
<el-carousel-item v-for="item in 4" :key="item">
|
||||
<h3 class="small justify-center" text="2xl">{{ item }}</h3>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</div>
|
||||
<p class="text-center demonstration">Vertical effect</p>
|
||||
<el-carousel
|
||||
height="200px"
|
||||
direction="vertical"
|
||||
motion-blur
|
||||
:autoplay="false"
|
||||
>
|
||||
<el-carousel-item v-for="item in 4" :key="item">
|
||||
<h3 text="2xl" justify="center">{{ item }}</h3>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.demonstration {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.el-carousel__item h3 {
|
||||
color: #475669;
|
||||
opacity: 0.75;
|
||||
line-height: 200px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.el-carousel__item:nth-child(2n) {
|
||||
background-color: #99a9bf;
|
||||
}
|
||||
|
||||
.el-carousel__item:nth-child(2n + 1) {
|
||||
background-color: #d3dce6;
|
||||
}
|
||||
</style>
|
||||
@@ -195,6 +195,28 @@ describe('Carousel', () => {
|
||||
await wait(60)
|
||||
expect(items[1].classList.contains('is-active')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('motion blur', async () => {
|
||||
const state = reactive({
|
||||
val: -1,
|
||||
oldVal: -1,
|
||||
})
|
||||
|
||||
wrapper = createComponent({
|
||||
onChange(val: number, prevVal: number) {
|
||||
state.val = val
|
||||
state.oldVal = prevVal
|
||||
},
|
||||
interval: 100,
|
||||
'motion-blur': true,
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
await wait(100)
|
||||
const items = wrapper.vm.$el.querySelectorAll('.el-transitioning')
|
||||
expect(items.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should guarantee order of indicators', async () => {
|
||||
const data = reactive([1, 2, 3, 4])
|
||||
wrapper = mount({
|
||||
|
||||
@@ -51,6 +51,10 @@ export const carouselProps = buildProps({
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
motionBlur: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
} as const)
|
||||
|
||||
export const carouselEmits = {
|
||||
|
||||
@@ -5,42 +5,46 @@
|
||||
@mouseenter.stop="handleMouseEnter"
|
||||
@mouseleave.stop="handleMouseLeave"
|
||||
>
|
||||
<div :class="ns.e('container')" :style="containerStyle">
|
||||
<transition v-if="arrowDisplay" name="carousel-arrow-left">
|
||||
<button
|
||||
v-show="
|
||||
(arrow === 'always' || hover) && (props.loop || activeIndex > 0)
|
||||
"
|
||||
type="button"
|
||||
:class="[ns.e('arrow'), ns.em('arrow', 'left')]"
|
||||
:aria-label="t('el.carousel.leftArrow')"
|
||||
@mouseenter="handleButtonEnter('left')"
|
||||
@mouseleave="handleButtonLeave"
|
||||
@click.stop="throttledArrowClick(activeIndex - 1)"
|
||||
>
|
||||
<ElIcon>
|
||||
<ArrowLeft />
|
||||
</ElIcon>
|
||||
</button>
|
||||
</transition>
|
||||
<transition v-if="arrowDisplay" name="carousel-arrow-right">
|
||||
<button
|
||||
v-show="
|
||||
(arrow === 'always' || hover) &&
|
||||
(props.loop || activeIndex < items.length - 1)
|
||||
"
|
||||
type="button"
|
||||
:class="[ns.e('arrow'), ns.em('arrow', 'right')]"
|
||||
:aria-label="t('el.carousel.rightArrow')"
|
||||
@mouseenter="handleButtonEnter('right')"
|
||||
@mouseleave="handleButtonLeave"
|
||||
@click.stop="throttledArrowClick(activeIndex + 1)"
|
||||
>
|
||||
<ElIcon>
|
||||
<ArrowRight />
|
||||
</ElIcon>
|
||||
</button>
|
||||
</transition>
|
||||
<transition v-if="arrowDisplay" name="carousel-arrow-left">
|
||||
<button
|
||||
v-show="
|
||||
(arrow === 'always' || hover) && (props.loop || activeIndex > 0)
|
||||
"
|
||||
type="button"
|
||||
:class="[ns.e('arrow'), ns.em('arrow', 'left')]"
|
||||
:aria-label="t('el.carousel.leftArrow')"
|
||||
@mouseenter="handleButtonEnter('left')"
|
||||
@mouseleave="handleButtonLeave"
|
||||
@click.stop="throttledArrowClick(activeIndex - 1)"
|
||||
>
|
||||
<ElIcon>
|
||||
<ArrowLeft />
|
||||
</ElIcon>
|
||||
</button>
|
||||
</transition>
|
||||
<transition v-if="arrowDisplay" name="carousel-arrow-right">
|
||||
<button
|
||||
v-show="
|
||||
(arrow === 'always' || hover) &&
|
||||
(props.loop || activeIndex < items.length - 1)
|
||||
"
|
||||
type="button"
|
||||
:class="[ns.e('arrow'), ns.em('arrow', 'right')]"
|
||||
:aria-label="t('el.carousel.rightArrow')"
|
||||
@mouseenter="handleButtonEnter('right')"
|
||||
@mouseleave="handleButtonLeave"
|
||||
@click.stop="throttledArrowClick(activeIndex + 1)"
|
||||
>
|
||||
<ElIcon>
|
||||
<ArrowRight />
|
||||
</ElIcon>
|
||||
</button>
|
||||
</transition>
|
||||
<div
|
||||
:class="carouselContainer"
|
||||
:style="containerStyle"
|
||||
@transitionend="handleTransitionEnd"
|
||||
>
|
||||
<PlaceholderItem />
|
||||
<slot />
|
||||
</div>
|
||||
@@ -65,6 +69,21 @@
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<svg
|
||||
v-if="props.motionBlur"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
style="display: none"
|
||||
>
|
||||
<defs>
|
||||
<filter id="elCarouselHorizontal">
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="12,0" />
|
||||
</filter>
|
||||
<filter id="elCarouselVertical">
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="0,10" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -95,9 +114,11 @@ const {
|
||||
containerStyle,
|
||||
handleButtonEnter,
|
||||
handleButtonLeave,
|
||||
isTransitioning,
|
||||
handleIndicatorClick,
|
||||
handleMouseEnter,
|
||||
handleMouseLeave,
|
||||
handleTransitionEnd,
|
||||
setActiveItem,
|
||||
prev,
|
||||
next,
|
||||
@@ -118,6 +139,18 @@ const carouselClasses = computed(() => {
|
||||
return classes
|
||||
})
|
||||
|
||||
const carouselContainer = computed(() => {
|
||||
const classes = [ns.e('container')]
|
||||
if (props.motionBlur && unref(isTransitioning)) {
|
||||
classes.push(
|
||||
unref(isVertical)
|
||||
? `${ns.namespace.value}-transitioning-vertical`
|
||||
: `${ns.namespace.value}-transitioning`
|
||||
)
|
||||
}
|
||||
return classes
|
||||
})
|
||||
|
||||
const indicatorsClasses = computed(() => {
|
||||
const classes = [ns.e('indicators'), ns.em('indicators', props.direction)]
|
||||
if (unref(hasLabel)) {
|
||||
|
||||
@@ -46,6 +46,8 @@ export const useCarousel = (
|
||||
const root = ref<HTMLDivElement>()
|
||||
const containerHeight = ref<number>(0)
|
||||
const isItemsTwoLength = ref(true)
|
||||
const isFirstCall = ref(true)
|
||||
const isTransitioning = ref(false)
|
||||
|
||||
// computed
|
||||
const arrowDisplay = computed(
|
||||
@@ -102,6 +104,11 @@ export const useCarousel = (
|
||||
}
|
||||
|
||||
const playSlides = () => {
|
||||
if (!isFirstCall.value) {
|
||||
isTransitioning.value = true
|
||||
}
|
||||
isFirstCall.value = false
|
||||
|
||||
if (activeIndex.value < items.value.length - 1) {
|
||||
activeIndex.value = activeIndex.value + 1
|
||||
} else if (props.loop) {
|
||||
@@ -110,6 +117,11 @@ export const useCarousel = (
|
||||
}
|
||||
|
||||
function setActiveItem(index: number | string) {
|
||||
if (!isFirstCall.value) {
|
||||
isTransitioning.value = true
|
||||
}
|
||||
isFirstCall.value = false
|
||||
|
||||
if (isString(index)) {
|
||||
const filteredItems = items.value.filter(
|
||||
(item) => item.props.name === index
|
||||
@@ -176,6 +188,10 @@ export const useCarousel = (
|
||||
startTimer()
|
||||
}
|
||||
|
||||
function handleTransitionEnd() {
|
||||
isTransitioning.value = false
|
||||
}
|
||||
|
||||
function handleButtonEnter(arrow: 'left' | 'right') {
|
||||
if (unref(isVertical)) return
|
||||
items.value.forEach((item, index) => {
|
||||
@@ -193,12 +209,20 @@ export const useCarousel = (
|
||||
}
|
||||
|
||||
function handleIndicatorClick(index: number) {
|
||||
if (index !== activeIndex.value) {
|
||||
if (!isFirstCall.value) {
|
||||
isTransitioning.value = true
|
||||
}
|
||||
}
|
||||
activeIndex.value = index
|
||||
}
|
||||
|
||||
function handleIndicatorHover(index: number) {
|
||||
if (props.trigger === 'hover' && index !== activeIndex.value) {
|
||||
activeIndex.value = index
|
||||
if (!isFirstCall.value) {
|
||||
isTransitioning.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,11 +343,13 @@ export const useCarousel = (
|
||||
hasLabel,
|
||||
hover,
|
||||
isCardType,
|
||||
isTransitioning,
|
||||
items,
|
||||
isVertical,
|
||||
containerStyle,
|
||||
isItemsTwoLength,
|
||||
handleButtonEnter,
|
||||
handleTransitionEnd,
|
||||
handleButtonLeave,
|
||||
handleIndicatorClick,
|
||||
handleMouseEnter,
|
||||
|
||||
@@ -178,3 +178,11 @@
|
||||
transform: translateY(-50%) translateX(10px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.#{$namespace}-transitioning {
|
||||
filter: url('#elCarouselHorizontal');
|
||||
}
|
||||
|
||||
.#{$namespace}-transitioning-vertical {
|
||||
filter: url('#elCarouselVertical');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user