mirror of
https://github.com/element-plus/element-plus.git
synced 2024-11-21 01:02:59 +08:00
feat(components): [carousel] If the carousel height is auto apply item height (#12388)
* fix(components): [carousel] :height is auto and card direction vertical * fix(components): [carousel] :height is auto and card direction vertical * fix(components): [carousel] :height is auto and card direction 垂直 * feat(components): [carousel] height is adaptive when height is auto * feat(components): [carousel] height is adaptive when height is auto * fix(components): [carousel] :height is auto and card direction 垂直 * fix(components): [carousel] :height is auto and card direction 垂直 * fix(components): [carousel] :height is auto test.tsx * fix(components): [carousel] :height is auto and card direction * fix: delete zIndex * fix: delete ts type * fix: add test * fix: css modification * fix: delete doc inside * fix: Revise card width not adaptive * fix: Modify calcCardTranslate * fix: test * fix: calcCardTranslate * fix: remove redundant code
This commit is contained in:
parent
ce1499b98e
commit
cd306117ae
@ -35,6 +35,16 @@ carousel/arrows
|
||||
|
||||
:::
|
||||
|
||||
## Auto height
|
||||
|
||||
When the `height` of `carousel` is set to `auto`, the `carousel` height will be automatically set according to the height of the `carousel item`
|
||||
|
||||
:::demo
|
||||
|
||||
carousel/auto-height
|
||||
|
||||
:::
|
||||
|
||||
## Card mode
|
||||
|
||||
When a page is wide enough but has limited height, you can activate card mode for carousels
|
||||
|
43
docs/examples/carousel/auto-height.vue
Normal file
43
docs/examples/carousel/auto-height.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div class="block text-center" style="height: 300px">
|
||||
<span class="demonstration">each carousel-item has a different height</span>
|
||||
<el-carousel height="auto" autoplay>
|
||||
<el-carousel-item style="height: 100px">
|
||||
<h3 class="small justify-center" text="2xl">height 100px</h3>
|
||||
</el-carousel-item>
|
||||
<el-carousel-item style="height: 200px">
|
||||
<h3 class="small justify-center" text="2xl">height 200px</h3>
|
||||
</el-carousel-item>
|
||||
<el-carousel-item style="height: 300px">
|
||||
<h3 class="small justify-center" text="2xl">height 300px</h3>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.carousel-item {
|
||||
color: #475669;
|
||||
opacity: 0.75;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.el-carousel__item h3 {
|
||||
color: #475669;
|
||||
opacity: 0.75;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.el-carousel__item:nth-child(2n) {
|
||||
background-color: #99a9bf;
|
||||
}
|
||||
|
||||
.el-carousel__item:nth-child(2n + 1) {
|
||||
background-color: #d3dce6;
|
||||
}
|
||||
</style>
|
@ -1,9 +1,21 @@
|
||||
<template>
|
||||
<p class="text-center demonstration">normal vertical layout</p>
|
||||
<el-carousel height="200px" direction="vertical" :autoplay="false">
|
||||
<el-carousel-item v-for="item in 4" :key="item">
|
||||
<h3 text="2xl" justify="center">{{ item }}</h3>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
<p class="text-center demonstration">card vertical layout</p>
|
||||
<el-carousel
|
||||
height="400px"
|
||||
direction="vertical"
|
||||
type="card"
|
||||
: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>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { nextTick, reactive } from 'vue'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import Carousel from '../src/carousel.vue'
|
||||
import CarouselItem from '../src/carousel-item.vue'
|
||||
|
||||
@ -221,4 +221,102 @@ describe('Carousel', () => {
|
||||
expect(indicators[index].element.textContent).toEqual(value.toString())
|
||||
})
|
||||
})
|
||||
it('height is set to auto', async () => {
|
||||
const data = [1, 2, 3]
|
||||
|
||||
wrapper = mount({
|
||||
setup() {
|
||||
return () => (
|
||||
<div>
|
||||
<Carousel height={'auto'} autoplay={false}>
|
||||
{data.map((value) => (
|
||||
<CarouselItem label={value} key={value} style="height: 100px">
|
||||
{value}
|
||||
</CarouselItem>
|
||||
))}
|
||||
</Carousel>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const items = wrapper.vm.$el.querySelectorAll('.el-carousel__item')
|
||||
|
||||
Array.from<HTMLElement>(items).forEach((item) => {
|
||||
vi.spyOn(item, 'offsetHeight', 'get').mockImplementation(() => {
|
||||
return Number.parseFloat(window.getComputedStyle(item).height) || 0
|
||||
})
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
expect(items[0].classList.contains('is-active')).toBeTruthy()
|
||||
|
||||
const container = wrapper.find<HTMLElement>(
|
||||
'.el-carousel__container'
|
||||
).element
|
||||
|
||||
expect(container.style.height).toBe('100px')
|
||||
})
|
||||
it('set to automatic when item is of different height', async () => {
|
||||
const data = [100, 200, 300]
|
||||
|
||||
wrapper = mount({
|
||||
setup() {
|
||||
return () => (
|
||||
<div>
|
||||
<Carousel height={'auto'} autoplay={false} ref={'carousel'}>
|
||||
{data.map((value) => (
|
||||
<CarouselItem
|
||||
label={value}
|
||||
key={value}
|
||||
style={`height: ${value}px`}
|
||||
>
|
||||
{value}
|
||||
</CarouselItem>
|
||||
))}
|
||||
</Carousel>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const items = wrapper.vm.$el.querySelectorAll('.el-carousel__item')
|
||||
|
||||
Array.from<HTMLElement>(items).forEach((item) => {
|
||||
vi.spyOn(item, 'offsetHeight', 'get').mockImplementation(() => {
|
||||
return Number.parseFloat(window.getComputedStyle(item).height) || 0
|
||||
})
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
const carousel = wrapper.findComponent({ ref: 'carousel' })
|
||||
.vm as CarouselInstance
|
||||
|
||||
const container = wrapper.find<HTMLElement>(
|
||||
'.el-carousel__container'
|
||||
).element
|
||||
|
||||
expect(items[0].classList.contains('is-active')).toBeTruthy()
|
||||
expect(container.style.height).toBe('100px')
|
||||
|
||||
carousel.next()
|
||||
await nextTick()
|
||||
|
||||
expect(items[1].classList.contains('is-active')).toBeTruthy()
|
||||
expect(container.style.height).toBe('200px')
|
||||
|
||||
carousel.next()
|
||||
await nextTick()
|
||||
|
||||
expect(items[2].classList.contains('is-active')).toBeTruthy()
|
||||
expect(container.style.height).toBe('300px')
|
||||
|
||||
carousel.next()
|
||||
await nextTick()
|
||||
|
||||
expect(items[0].classList.contains('is-active')).toBeTruthy()
|
||||
expect(container.style.height).toBe('100px')
|
||||
})
|
||||
})
|
||||
|
@ -1,13 +1,17 @@
|
||||
<template>
|
||||
<div
|
||||
v-show="ready"
|
||||
ref="carouselItemRef"
|
||||
:class="[
|
||||
ns.e('item'),
|
||||
ns.is('active', active),
|
||||
ns.is('in-stage', inStage),
|
||||
ns.is('hover', hover),
|
||||
ns.is('animating', animating),
|
||||
{ [ns.em('item', 'card')]: isCardType },
|
||||
{
|
||||
[ns.em('item', 'card')]: isCardType,
|
||||
[ns.em('item', 'card-vertical')]: isCardType && isVertical,
|
||||
},
|
||||
]"
|
||||
:style="itemStyle"
|
||||
@click="handleItemClick"
|
||||
@ -34,6 +38,7 @@ const ns = useNamespace('carousel')
|
||||
const COMPONENT_NAME = 'ElCarouselItem'
|
||||
// inject
|
||||
const {
|
||||
carouselItemRef,
|
||||
active,
|
||||
animating,
|
||||
hover,
|
||||
|
@ -5,7 +5,7 @@
|
||||
@mouseenter.stop="handleMouseEnter"
|
||||
@mouseleave.stop="handleMouseLeave"
|
||||
>
|
||||
<div :class="ns.e('container')" :style="{ height: height }">
|
||||
<div :class="ns.e('container')" :style="containerStyle">
|
||||
<transition v-if="arrowDisplay" name="carousel-arrow-left">
|
||||
<button
|
||||
v-show="
|
||||
@ -84,6 +84,8 @@ const {
|
||||
hover,
|
||||
isCardType,
|
||||
items,
|
||||
isVertical,
|
||||
containerStyle,
|
||||
handleButtonEnter,
|
||||
handleButtonLeave,
|
||||
handleIndicatorClick,
|
||||
@ -110,9 +112,12 @@ const indicatorsClasses = computed(() => {
|
||||
if (unref(hasLabel)) {
|
||||
classes.push(ns.em('indicators', 'labels'))
|
||||
}
|
||||
if (props.indicatorPosition === 'outside' || unref(isCardType)) {
|
||||
if (props.indicatorPosition === 'outside') {
|
||||
classes.push(ns.em('indicators', 'outside'))
|
||||
}
|
||||
if (unref(isVertical)) {
|
||||
classes.push(ns.em('indicators', 'right'))
|
||||
}
|
||||
return classes
|
||||
})
|
||||
|
||||
|
@ -28,6 +28,7 @@ export type CarouselContext = {
|
||||
addItem: (item: CarouselItemContext) => void
|
||||
removeItem: (uid: number) => void
|
||||
setActiveItem: (index: number) => void
|
||||
setContainerHeight: (height: number) => void
|
||||
}
|
||||
|
||||
export const carouselContextKey: InjectionKey<CarouselContext> =
|
||||
|
@ -35,6 +35,7 @@ export const useCarouselItem = (
|
||||
|
||||
const CARD_SCALE = 0.83
|
||||
|
||||
const carouselItemRef = ref<HTMLElement>()
|
||||
const hover = ref(false)
|
||||
const translate = ref(0)
|
||||
const scale = ref(1)
|
||||
@ -67,7 +68,10 @@ export const useCarouselItem = (
|
||||
}
|
||||
|
||||
function calcCardTranslate(index: number, activeIndex: number) {
|
||||
const parentWidth = carouselContext.root.value?.offsetWidth || 0
|
||||
const parentWidth = unref(isVertical)
|
||||
? carouselContext.root.value?.offsetHeight || 0
|
||||
: carouselContext.root.value?.offsetWidth || 0
|
||||
|
||||
if (inStage.value) {
|
||||
return (parentWidth * ((2 - CARD_SCALE) * (index - activeIndex) + 1)) / 4
|
||||
} else if (index < activeIndex) {
|
||||
@ -111,12 +115,6 @@ export const useCarouselItem = (
|
||||
active.value = isActive
|
||||
|
||||
if (_isCardType) {
|
||||
if (_isVertical) {
|
||||
debugWarn(
|
||||
'Carousel',
|
||||
'vertical direction is not supported for card mode'
|
||||
)
|
||||
}
|
||||
inStage.value = Math.round(Math.abs(index - activeIndex)) <= 1
|
||||
translate.value = calcCardTranslate(index, activeIndex)
|
||||
scale.value = unref(active) ? 1 : CARD_SCALE
|
||||
@ -125,6 +123,10 @@ export const useCarouselItem = (
|
||||
}
|
||||
|
||||
ready.value = true
|
||||
|
||||
if (isActive && carouselItemRef.value) {
|
||||
carouselContext.setContainerHeight(carouselItemRef.value.offsetHeight)
|
||||
}
|
||||
}
|
||||
|
||||
function handleItemClick() {
|
||||
@ -159,6 +161,7 @@ export const useCarouselItem = (
|
||||
})
|
||||
|
||||
return {
|
||||
carouselItemRef,
|
||||
active,
|
||||
animating,
|
||||
hover,
|
||||
|
@ -40,6 +40,7 @@ export const useCarousel = (
|
||||
const timer = ref<ReturnType<typeof setInterval> | null>(null)
|
||||
const hover = ref(false)
|
||||
const root = ref<HTMLDivElement>()
|
||||
const containerHeight = ref<number>(0)
|
||||
|
||||
// computed
|
||||
const arrowDisplay = computed(
|
||||
@ -53,6 +54,18 @@ export const useCarousel = (
|
||||
const isCardType = computed(() => props.type === 'card')
|
||||
const isVertical = computed(() => props.direction === 'vertical')
|
||||
|
||||
const containerStyle = computed(() => {
|
||||
if (props.height !== 'auto') {
|
||||
return {
|
||||
height: props.height,
|
||||
}
|
||||
}
|
||||
return {
|
||||
height: `${containerHeight.value}px`,
|
||||
overflow: 'hidden',
|
||||
}
|
||||
})
|
||||
|
||||
// methods
|
||||
const throttledArrowClick = throttle(
|
||||
(index: number) => {
|
||||
@ -192,6 +205,11 @@ export const useCarousel = (
|
||||
startTimer()
|
||||
}
|
||||
|
||||
function setContainerHeight(height: number) {
|
||||
if (props.height !== 'auto') return
|
||||
containerHeight.value = height
|
||||
}
|
||||
|
||||
// watch
|
||||
watch(
|
||||
() => activeIndex.value,
|
||||
@ -253,6 +271,7 @@ export const useCarousel = (
|
||||
addItem,
|
||||
removeItem,
|
||||
setActiveItem,
|
||||
setContainerHeight,
|
||||
})
|
||||
|
||||
return {
|
||||
@ -263,6 +282,8 @@ export const useCarousel = (
|
||||
hover,
|
||||
isCardType,
|
||||
items,
|
||||
isVertical,
|
||||
containerStyle,
|
||||
handleButtonEnter,
|
||||
handleButtonLeave,
|
||||
handleIndicatorClick,
|
||||
|
@ -23,18 +23,26 @@
|
||||
@include m(card) {
|
||||
width: 50%;
|
||||
transition: transform 0.4s ease-in-out;
|
||||
|
||||
&.is-in-stage {
|
||||
cursor: pointer;
|
||||
z-index: getCssVar('index', 'normal');
|
||||
|
||||
&:hover .#{$namespace}-carousel__mask,
|
||||
&.is-hover .#{$namespace}-carousel__mask {
|
||||
opacity: 0.12;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
z-index: calc(getCssVar('index', 'normal') + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@include m(card-vertical) {
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@include e(mask) {
|
||||
|
@ -11,11 +11,11 @@
|
||||
position: relative;
|
||||
|
||||
@include m(horizontal) {
|
||||
overflow-x: hidden;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@include m(vertical) {
|
||||
overflow-y: hidden;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@include e(container) {
|
||||
@ -88,15 +88,21 @@
|
||||
text-align: center;
|
||||
position: static;
|
||||
transform: none;
|
||||
|
||||
.#{$namespace}-carousel__indicator:hover button {
|
||||
opacity: 0.64;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: getCssVar('carousel', 'indicator-out-color');
|
||||
opacity: 0.24;
|
||||
}
|
||||
}
|
||||
|
||||
@include m(right) {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@include m(labels) {
|
||||
left: 0;
|
||||
right: 0;
|
||||
@ -134,6 +140,7 @@
|
||||
@include m(vertical) {
|
||||
padding: getCssVar('carousel-indicator-padding-horizontal')
|
||||
getCssVar('carousel-indicator-padding-vertical');
|
||||
|
||||
.#{$namespace}-carousel__button {
|
||||
width: getCssVar('carousel-indicator-height');
|
||||
height: calc(#{getCssVar('carousel-indicator-width')} / 2);
|
||||
|
Loading…
Reference in New Issue
Block a user