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:
Jed
2024-02-29 14:54:06 +08:00
committed by GitHub
parent 3f4bee5a90
commit 53bc6517f2
7 changed files with 196 additions and 49 deletions

View File

@@ -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

View 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>

View File

@@ -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({

View File

@@ -51,6 +51,10 @@ export const carouselProps = buildProps({
type: Boolean,
default: true,
},
motionBlur: {
type: Boolean,
default: false,
},
} as const)
export const carouselEmits = {

View File

@@ -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)) {

View File

@@ -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,

View File

@@ -178,3 +178,11 @@
transform: translateY(-50%) translateX(10px);
opacity: 0;
}
.#{$namespace}-transitioning {
filter: url('#elCarouselHorizontal');
}
.#{$namespace}-transitioning-vertical {
filter: url('#elCarouselVertical');
}