mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-02 03:58:07 +08:00
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:
parent
5dd69e55c1
commit
b69f9c5b08
@ -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 渲染器 |
|
| type | `string` | `"carousel"` | 指定为 Carousel 渲染器 |
|
||||||
| className | `string` | `"panel-default"` | 外层 Dom 的类名 |
|
| className | `string` | `"panel-default"` | 外层 Dom 的类名 |
|
||||||
| options | `array` | `[]` | 轮播面板数据 |
|
| options | `array` | `[]` | 轮播面板数据 |
|
||||||
@ -119,13 +150,16 @@ itemSchema: {
|
|||||||
| itemSchema | `object` | | 自定义`schema`来展示数据 |
|
| itemSchema | `object` | | 自定义`schema`来展示数据 |
|
||||||
| auto | `boolean` | `true` | 是否自动轮播 |
|
| auto | `boolean` | `true` | 是否自动轮播 |
|
||||||
| interval | `string` | `5s` | 切换动画间隔 |
|
| interval | `string` | `5s` | 切换动画间隔 |
|
||||||
| duration | `string` | `0.5s` | 切换动画时长 |
|
| duration | `number` | `500` | 切换动画时长(ms) |
|
||||||
| width | `string` | `auto` | 宽度 |
|
| width | `string` | `auto` | 宽度 |
|
||||||
| height | `string` | `200px` | 高度 |
|
| height | `string` | `200px` | 高度 |
|
||||||
| controls | `array` | `['dots', 'arrows']` | 显示左右箭头、底部圆点索引 |
|
| controls | `array` | `['dots', 'arrows']` | 显示左右箭头、底部圆点索引 |
|
||||||
| controlsTheme | `string` | `light` | 左右箭头、底部圆点索引颜色,默认`light`,另有`dark`模式 |
|
| controlsTheme | `string` | `light` | 左右箭头、底部圆点索引颜色,默认`light`,另有`dark`模式 |
|
||||||
| animation | `string` | fade | 切换动画效果,默认`fade`,另有`slide`模式 |
|
| animation | `string` | fade | 切换动画效果,默认`fade`,另有`slide`模式 |
|
||||||
| thumbMode | `string` | `"cover" \| "contain"` | 图片默认缩放模式 |
|
| 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` |
|
||||||
|
|
||||||
## 事件表
|
## 事件表
|
||||||
|
|
||||||
|
@ -176,6 +176,15 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.#{$ns}Carousel-arrow--always {
|
||||||
|
.#{$ns}Carousel-leftArrow {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.#{$ns}Carousel-rightArrow {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.#{$ns}Carousel-leftArrow {
|
.#{$ns}Carousel-leftArrow {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -2,7 +2,8 @@ import React from 'react';
|
|||||||
import Transition, {
|
import Transition, {
|
||||||
ENTERED,
|
ENTERED,
|
||||||
ENTERING,
|
ENTERING,
|
||||||
EXITING
|
EXITING,
|
||||||
|
EXITED
|
||||||
} from 'react-transition-group/Transition';
|
} from 'react-transition-group/Transition';
|
||||||
import {Renderer, RendererProps} from 'amis-core';
|
import {Renderer, RendererProps} from 'amis-core';
|
||||||
import {resolveVariableAndFilter} from 'amis-core';
|
import {resolveVariableAndFilter} from 'amis-core';
|
||||||
@ -88,6 +89,26 @@ export interface CarouselSchema extends BaseSchema {
|
|||||||
* 配置固定值
|
* 配置固定值
|
||||||
*/
|
*/
|
||||||
options?: Array<any>;
|
options?: Array<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否一直显示箭头
|
||||||
|
*/
|
||||||
|
alwaysShowArrow?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多图模式配置项
|
||||||
|
*/
|
||||||
|
multiple?: {
|
||||||
|
count: number
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义箭头图标
|
||||||
|
*/
|
||||||
|
icons?: {
|
||||||
|
prev?: SchemaCollection;
|
||||||
|
next?: SchemaCollection;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const animationStyles: {
|
const animationStyles: {
|
||||||
@ -108,6 +129,7 @@ export interface CarouselState {
|
|||||||
current: number;
|
current: number;
|
||||||
options: any[];
|
options: any[];
|
||||||
nextAnimation: string;
|
nextAnimation: string;
|
||||||
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultSchema = {
|
const defaultSchema = {
|
||||||
@ -156,6 +178,8 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
|
|||||||
| 'animation'
|
| 'animation'
|
||||||
| 'controls'
|
| 'controls'
|
||||||
| 'placeholder'
|
| 'placeholder'
|
||||||
|
| 'multiple'
|
||||||
|
| 'alwaysShowArrow'
|
||||||
> = {
|
> = {
|
||||||
auto: true,
|
auto: true,
|
||||||
interval: 5000,
|
interval: 5000,
|
||||||
@ -163,13 +187,16 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
|
|||||||
controlsTheme: 'light',
|
controlsTheme: 'light',
|
||||||
animation: 'fade',
|
animation: 'fade',
|
||||||
controls: ['dots', 'arrows'],
|
controls: ['dots', 'arrows'],
|
||||||
placeholder: '-'
|
placeholder: '-',
|
||||||
|
multiple: {count: 1},
|
||||||
|
alwaysShowArrow: false
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
current: 0,
|
current: 0,
|
||||||
options: this.props.options || getPropValue(this.props) || [],
|
options: this.props.options || getPropValue(this.props) || [],
|
||||||
nextAnimation: ''
|
nextAnimation: '',
|
||||||
|
loading: false
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -294,11 +321,19 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
|
|||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
next() {
|
next() {
|
||||||
|
const multiple = this.props.multiple;
|
||||||
|
if (this.state.loading && multiple && multiple.count > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.autoSlide('next');
|
this.autoSlide('next');
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
prev() {
|
prev() {
|
||||||
|
const multiple = this.props.multiple;
|
||||||
|
if (this.state.loading && multiple && multiple.count > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.autoSlide('prev');
|
this.autoSlide('prev');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,8 +345,12 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
|
|||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
async changeSlide(index: number) {
|
async changeSlide(index: number) {
|
||||||
const {current} = this.state;
|
const {current, loading} = this.state;
|
||||||
const {dispatchEvent, data} = this.props;
|
const {dispatchEvent, data, multiple} = this.props;
|
||||||
|
|
||||||
|
if (loading && multiple && multiple.count > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const rendererEvent = await dispatchEvent(
|
const rendererEvent = await dispatchEvent(
|
||||||
'change',
|
'change',
|
||||||
@ -369,14 +408,37 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
|
|||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleMouseEnter() {
|
handleMouseEnter() {
|
||||||
|
const multiple = this.props.multiple;
|
||||||
|
if (multiple && multiple.count > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.clearAutoTimeout();
|
this.clearAutoTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleMouseLeave() {
|
handleMouseLeave() {
|
||||||
|
const multiple = this.props.multiple;
|
||||||
|
if (multiple && multiple.count > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.prepareAutoSlide();
|
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() {
|
render() {
|
||||||
const {
|
const {
|
||||||
render,
|
render,
|
||||||
@ -391,9 +453,13 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
|
|||||||
controlsTheme,
|
controlsTheme,
|
||||||
placeholder,
|
placeholder,
|
||||||
data,
|
data,
|
||||||
name
|
name,
|
||||||
|
duration,
|
||||||
|
multiple,
|
||||||
|
alwaysShowArrow,
|
||||||
|
icons
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {options, current, nextAnimation} = this.state;
|
const {options, current, nextAnimation, loading} = this.state;
|
||||||
|
|
||||||
let body: JSX.Element | null = null;
|
let body: JSX.Element | null = null;
|
||||||
let carouselStyles: {
|
let carouselStyles: {
|
||||||
@ -406,8 +472,17 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
|
|||||||
controls!.indexOf('arrows') > -1
|
controls!.indexOf('arrows') > -1
|
||||||
];
|
];
|
||||||
const animationName = nextAnimation || animation;
|
const animationName = nextAnimation || animation;
|
||||||
|
|
||||||
if (Array.isArray(options) && options.length) {
|
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 = (
|
body = (
|
||||||
<div
|
<div
|
||||||
ref={this.wrapperRef}
|
ref={this.wrapperRef}
|
||||||
@ -420,7 +495,7 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
|
|||||||
mountOnEnter
|
mountOnEnter
|
||||||
unmountOnExit
|
unmountOnExit
|
||||||
in={key === current}
|
in={key === current}
|
||||||
timeout={500}
|
timeout={timeout}
|
||||||
key={key}
|
key={key}
|
||||||
>
|
>
|
||||||
{(status: string) => {
|
{(status: string) => {
|
||||||
@ -430,6 +505,40 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
|
|||||||
(item: HTMLElement) => item.offsetHeight
|
(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 (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -438,20 +547,19 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
|
|||||||
animationName,
|
animationName,
|
||||||
animationStyles[status]
|
animationStyles[status]
|
||||||
)}
|
)}
|
||||||
|
style={itemStyle}
|
||||||
>
|
>
|
||||||
{render(
|
{multipleCount === 1 ? itemRender(option) : null}
|
||||||
`${current}/body`,
|
{multipleCount > 1 ?
|
||||||
itemSchema ? itemSchema : (defaultSchema as any),
|
newOptions[key].map((option: any, index: number) => (
|
||||||
{
|
<div key={index} style={{
|
||||||
thumbMode: this.props.thumbMode,
|
width: 100 / multipleCount + '%',
|
||||||
data: createObject(
|
height: '100%',
|
||||||
data,
|
float: 'left'
|
||||||
isObject(option)
|
}}>
|
||||||
? option
|
{itemRender(option)}
|
||||||
: {item: option, [name!]: option}
|
</div>
|
||||||
)
|
)) : null}
|
||||||
}
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
@ -463,7 +571,7 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(`Carousel Carousel--${controlsTheme}`, className)}
|
className={cx(`Carousel Carousel--${controlsTheme}`, {['Carousel-arrow--always']: !!alwaysShowArrow}, className)}
|
||||||
style={carouselStyles}
|
style={carouselStyles}
|
||||||
>
|
>
|
||||||
{body ? body : placeholder}
|
{body ? body : placeholder}
|
||||||
@ -471,12 +579,16 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
|
|||||||
{dots ? this.renderDots() : null}
|
{dots ? this.renderDots() : null}
|
||||||
{arrows ? (
|
{arrows ? (
|
||||||
<div className={cx('Carousel-leftArrow')} onClick={this.prev}>
|
<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>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{arrows ? (
|
{arrows ? (
|
||||||
<div className={cx('Carousel-rightArrow')} onClick={this.next}>
|
<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>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user