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:
热爱vue的小菜鸟 2023-05-14 17:47:43 +08:00 committed by GitHub
parent ce1499b98e
commit cd306117ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 226 additions and 13 deletions

View File

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

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

View File

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

View File

@ -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')
})
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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