feat:carousel支持卡片动画模式 (#6354)

* feat:carousel支持多图配置

* Update carousel.md

---------

Co-authored-by: zhaowenli <zhaowenli@baidu.com>
Co-authored-by: RUNZE LU <36724300+lurunze1226@users.noreply.github.com>
This commit is contained in:
xiangwaner 2023-03-14 15:10:14 +08:00 committed by GitHub
parent 5dd69e55c1
commit b69f9c5b08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 183 additions and 28 deletions

View File

@ -101,10 +101,41 @@ itemSchema: {
}
```
## 多图模式
> `2.8.1` 及以上版本
```schema: scope="body"
{
"type": "carousel",
"auto": true,
"thumbMode": "cover",
"animation": "slide",
"multiple": {count: 3},
"interval": 0,
"duration": 5000,
"height": 300,
"options": [
{
"image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692722/4f3cb4202335.jpeg@s_0,w_216,l_1,f_jpg,q_80"
},
{
"image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692942/d8e4992057f9.jpeg@s_0,w_216,l_1,f_jpg,q_80"
},
{
"image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693148/1314a2a3d3f6.jpeg@s_0,w_216,l_1,f_jpg,q_80"
},
{
"image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693379/8f2e79f82be0.jpeg@s_0,w_216,l_1,f_jpg,q_80"
}
]
}
```
## 属性表
| 属性名 | 类型 | 默认值 | 说明 |
| ---------------------------- | --------- | ---------------------- | ------------------------------------------------------- |
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
| ---------------------------- | --------- | ---------------------- | ------------------------------------------------------- | --- |
| type | `string` | `"carousel"` | 指定为 Carousel 渲染器 |
| className | `string` | `"panel-default"` | 外层 Dom 的类名 |
| options | `array` | `[]` | 轮播面板数据 |
@ -119,13 +150,16 @@ itemSchema: {
| itemSchema | `object` | | 自定义`schema`来展示数据 |
| auto | `boolean` | `true` | 是否自动轮播 |
| interval | `string` | `5s` | 切换动画间隔 |
| duration | `string` | `0.5s` | 切换动画时长 |
| duration | `number` | `500` | 切换动画时长ms |
| width | `string` | `auto` | 宽度 |
| height | `string` | `200px` | 高度 |
| controls | `array` | `['dots', 'arrows']` | 显示左右箭头、底部圆点索引 |
| controlsTheme | `string` | `light` | 左右箭头、底部圆点索引颜色,默认`light`,另有`dark`模式 |
| animation | `string` | fade | 切换动画效果,默认`fade`,另有`slide`模式 |
| thumbMode | `string` | `"cover" \| "contain"` | 图片默认缩放模式 |
| multiple | `object` | `{count: 1}` | 多图模式count表示展示的数量 | `2.8.1` |
| alwaysShowArrow | `boolean` | `false` | 是否一直显示箭头为false时鼠标hover才会显示 | `2.8.1` |
| icons | {prev: `SchemaCollection`; next: `SchemaCollection`;} | | 自定义箭头图标 | `2.8.1` |
## 事件表

View File

@ -176,6 +176,15 @@
right: 0;
}
&.#{$ns}Carousel-arrow--always {
.#{$ns}Carousel-leftArrow {
display: block;
}
.#{$ns}Carousel-rightArrow {
display: block;
}
}
&:hover {
.#{$ns}Carousel-leftArrow {
display: block;

View File

@ -2,7 +2,8 @@ import React from 'react';
import Transition, {
ENTERED,
ENTERING,
EXITING
EXITING,
EXITED
} from 'react-transition-group/Transition';
import {Renderer, RendererProps} from 'amis-core';
import {resolveVariableAndFilter} from 'amis-core';
@ -88,6 +89,26 @@ export interface CarouselSchema extends BaseSchema {
*
*/
options?: Array<any>;
/**
*
*/
alwaysShowArrow?: boolean;
/**
*
*/
multiple?: {
count: number
};
/**
*
*/
icons?: {
prev?: SchemaCollection;
next?: SchemaCollection;
};
}
const animationStyles: {
@ -108,6 +129,7 @@ export interface CarouselState {
current: number;
options: any[];
nextAnimation: string;
loading: boolean;
}
const defaultSchema = {
@ -156,6 +178,8 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
| 'animation'
| 'controls'
| 'placeholder'
| 'multiple'
| 'alwaysShowArrow'
> = {
auto: true,
interval: 5000,
@ -163,13 +187,16 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
controlsTheme: 'light',
animation: 'fade',
controls: ['dots', 'arrows'],
placeholder: '-'
placeholder: '-',
multiple: {count: 1},
alwaysShowArrow: false
};
state = {
current: 0,
options: this.props.options || getPropValue(this.props) || [],
nextAnimation: ''
nextAnimation: '',
loading: false
};
componentDidMount() {
@ -294,11 +321,19 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
@autobind
next() {
const multiple = this.props.multiple;
if (this.state.loading && multiple && multiple.count > 1) {
return;
}
this.autoSlide('next');
}
@autobind
prev() {
const multiple = this.props.multiple;
if (this.state.loading && multiple && multiple.count > 1) {
return;
}
this.autoSlide('prev');
}
@ -310,8 +345,12 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
@autobind
async changeSlide(index: number) {
const {current} = this.state;
const {dispatchEvent, data} = this.props;
const {current, loading} = this.state;
const {dispatchEvent, data, multiple} = this.props;
if (loading && multiple && multiple.count > 1) {
return;
}
const rendererEvent = await dispatchEvent(
'change',
@ -369,14 +408,37 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
@autobind
handleMouseEnter() {
const multiple = this.props.multiple;
if (multiple && multiple.count > 1) {
return;
}
this.clearAutoTimeout();
}
@autobind
handleMouseLeave() {
const multiple = this.props.multiple;
if (multiple && multiple.count > 1) {
return;
}
this.prepareAutoSlide();
}
// 处理options
getNewOptions(options: any, count: number = 1) {
let newOptions: Array<any> = options;
if (Array.isArray(options) && options.length) {
newOptions = new Array(options.length);
for (let i = 0; i < options.length; i++) {
newOptions[i] = new Array(count);
for(let j = 0; j < count; j++) {
newOptions[i][j] = options[(i + j) % options.length];
}
}
}
return newOptions;
}
render() {
const {
render,
@ -391,9 +453,13 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
controlsTheme,
placeholder,
data,
name
name,
duration,
multiple,
alwaysShowArrow,
icons
} = this.props;
const {options, current, nextAnimation} = this.state;
const {options, current, nextAnimation, loading} = this.state;
let body: JSX.Element | null = null;
let carouselStyles: {
@ -408,6 +474,15 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
const animationName = nextAnimation || animation;
if (Array.isArray(options) && options.length) {
let multipleCount = 1;
if (multiple && typeof multiple.count === 'number' && multiple.count >= 2) {
multipleCount = Math.floor(multiple.count) < options.length ? Math.floor(multiple.count) : options.length;
}
const newOptions = this.getNewOptions(options, multipleCount);
const transitionDuration = multipleCount > 1 && typeof duration === 'number'
? `${duration}ms`: (duration || '500ms');
const timeout = multipleCount > 1 && typeof duration === 'number' ? duration : 500;
body = (
<div
ref={this.wrapperRef}
@ -420,7 +495,7 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
mountOnEnter
unmountOnExit
in={key === current}
timeout={500}
timeout={timeout}
key={key}
>
{(status: string) => {
@ -430,6 +505,40 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
(item: HTMLElement) => item.offsetHeight
);
}
if (multipleCount > 1) {
if ((status === ENTERING || status === EXITING) && !loading) {
this.setState({loading: true});
} else if ((status === ENTERED || status === EXITED) && loading) {
this.setState({loading: false});
}
}
const transformStyles: {
[propName: string]: number;
} = {
[ENTERING]: 0,
[ENTERED]: 0,
[EXITING]: animationName === 'slideRight' ? 100 / multipleCount : -100 / multipleCount,
[EXITED]: animationName === 'slideRight' ? -100 / multipleCount : 100 / multipleCount
};
const itemStyle = multipleCount > 1 ? {
transitionTimingFunction: 'linear',
transitionDuration: transitionDuration,
...(animation === 'slide' ? {transform: `translateX(${transformStyles[status]}%)`} : {})
} : {};
const itemRender = (option: any) => render(
`${current}/body`,
itemSchema ? itemSchema : (defaultSchema as any),
{
thumbMode: this.props.thumbMode,
data: createObject(
data,
isObject(option)
? option
: {item: option, [name!]: option}
)
}
);
return (
<div
@ -438,20 +547,19 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
animationName,
animationStyles[status]
)}
style={itemStyle}
>
{render(
`${current}/body`,
itemSchema ? itemSchema : (defaultSchema as any),
{
thumbMode: this.props.thumbMode,
data: createObject(
data,
isObject(option)
? option
: {item: option, [name!]: option}
)
}
)}
{multipleCount === 1 ? itemRender(option) : null}
{multipleCount > 1 ?
newOptions[key].map((option: any, index: number) => (
<div key={index} style={{
width: 100 / multipleCount + '%',
height: '100%',
float: 'left'
}}>
{itemRender(option)}
</div>
)) : null}
</div>
);
}}
@ -463,7 +571,7 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
return (
<div
className={cx(`Carousel Carousel--${controlsTheme}`, className)}
className={cx(`Carousel Carousel--${controlsTheme}`, {['Carousel-arrow--always']: !!alwaysShowArrow}, className)}
style={carouselStyles}
>
{body ? body : placeholder}
@ -471,12 +579,16 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
{dots ? this.renderDots() : null}
{arrows ? (
<div className={cx('Carousel-leftArrow')} onClick={this.prev}>
<Icon icon="left-arrow" className="icon" />
{icons && icons.prev
? React.isValidElement(icons.prev) ? icons.prev : render('arrow-prev', icons.prev)
: (<Icon icon="left-arrow" className="icon" />)}
</div>
) : null}
{arrows ? (
<div className={cx('Carousel-rightArrow')} onClick={this.next}>
<Icon icon="right-arrow" className="icon" />
{icons && icons.next
? React.isValidElement(icons.next) ? icons.next : render('arrow-next', icons.next)
: (<Icon icon="right-arrow" className="icon" />)}
</div>
) : null}
</div>