QueueAnim 进出场动画

通过简单的配置对一组元素添加串行的进场动画效果。

何时使用#

  • 从内容A到内容B的转变过程时能有效的吸引用户注意力,突出视觉中心,提高整体视觉效果。

  • 小的信息元素排布或块状较多的情况下,根据一定的路径层次依次进场,区分维度层级,来凸显量级,使页面转场更加流畅和舒适,提高整体视觉效果和产品的质感。

  • 特别适合首页和需要视觉展示效果的宣传页,以及单页应用的切换页面动效。

API#

此组件取代了 0.9.x 版本的 enter-animation

元素依次进场。

<QueueAnim>
  <div key='demo1'>依次进场</div>
  <div key='demo2'>依次进场</div>
  <div key='demo3'>依次进场</div>
  <div key='demo4'>依次进场</div>
</QueueAnim>

每个子标签必须带 key,如果未设置 key 将不执行动画。

参数 类型 默认 详细
type string / array right 动画内置参数
left right top bottom scale scaleBig scaleX scaleY
animConfig object / array null 配置动画参数
{opacity:[1, 0],translateY:[0, -30]} 具体参考 velocity 的写法
delay number / array 0 整个动画的延时,以毫秒为单位
duration number / array 500 每个动画的时间,以毫秒为单位
interval number / array 100 每个动画的间隔时间,以毫秒为单位
leaveReverse boolean false 出场时是否倒放,从最后一个 dom 开始往上播放
ease string / array easeOutQuart 动画的缓动函数,查看详细
animatingClassName array ['queue-anim-entering', 'queue-anim-leaving'] 进出场动画进行中的类名
component string div QueueAnim 替换的标签名

当以上数据类型为 Array 时,['left', 'top'] 第一个为进场动画属性, 第二个为离场属性。

代码演示

import { QueueAnim } from 'antd';

ReactDOM.render(
  <QueueAnim delay={500} style={{ height: 150 }}>
    <div key="a">依次进场</div>
    <div key="b">依次进场</div>
    <div key="c">依次进场</div>
    <div key="d">依次进场</div>
    <div key="e">依次进场</div>
    <div key="f">依次进场</div>
  </QueueAnim>
, mountNode);
.code-box-demo > div {
  overflow: hidden;
}

.code-box-demo .buttons {
  text-align: center;
  margin: 0 auto;
  margin-bottom: 20px;
}

最简单的进场例子。

import { QueueAnim, Button } from 'antd';

const Test = React.createClass({
  getInitialState() {
    return {
      show: true
    };
  },
  onClick() {
    this.setState({
      show: !this.state.show
    });
  },
  render() {
    const list = this.state.show ? [
      <div className="demo-kp" key="a">
        <ul>
          <li></li>
          <li></li>
          <li></li>
        </ul>
      </div>,
      <div className="demo-listBox" key="b">
        <div className="demo-list">
        <div className="title"></div>
          <ul>
            <li></li>
            <li></li>
            <li></li>
          </ul>
        </div>
      </div>
    ] : null;
    return (
      <div>
        <p className="buttons">
          <Button type="primary" onClick={this.onClick}>切换</Button>
        </p>
        <QueueAnim className="demo-content"
          key="demo"
          type={['right', 'left']}
          ease={['easeOutQuart', 'easeInOutQuart']}>
          {list}
        </QueueAnim>
      </div>
    );
  }
});

ReactDOM.render(<Test />, mountNode);

通过把属性设置一个数组来分别表示进出场的效果,typeanimConfigdelaydurationintervalease 等属性均支持配置为数组。

import { QueueAnim, Button, Radio, Input, Form, Row, Col } from 'antd';
const FormItem = Form.Item;
const RadioGroup = Radio.Group;

const Test = React.createClass({
  getInitialState() {
    return {
      show: true
    };
  },
  onClick() {
    this.setState({
      show: !this.state.show
    });
  },
  render() {
    const formItemLayout = {
      labelCol: { span: 6 },
      wrapperCol: { span: 14 },
    };
    return (
      <div>
        <p className="buttons">
          <Button type="primary" onClick={this.onClick}>切换</Button>
        </p>
        <QueueAnim component={Form} className="ant-form ant-form-horizontal" type="bottom" leaveReverse>
          {this.state.show ? [
            <FormItem key="item1" {...formItemLayout} label="用户名:">
              <p className="ant-form-text">大眼萌 minion</p>
            </FormItem>,
            <FormItem key="item2" {...formItemLayout} label="密码:">
              <Input type="password" placeholder="请输入密码" />
            </FormItem>,
            <FormItem key="item3" {...formItemLayout} label="您的性别:">
              <RadioGroup>
                <Radio value="male">男的</Radio>
                <Radio value="female">女的</Radio>
              </RadioGroup>
            </FormItem>,
            <FormItem key="item4" {...formItemLayout} label="备注:">
              <Input type="textarea" placeholder="随便写" />
            </FormItem>,
            <Row key="submit">
              <Col span="16" offset="6">
                <Button type="primary" htmlType="submit">确定</Button>
              </Col>
            </Row>,
          ] : null}
        </QueueAnim>
      </div>
    );
  }
});

ReactDOM.render(<Test />, mountNode);

表单组合的进场与出场动画。

import { QueueAnim, Button } from 'antd';

const Test = React.createClass({
  getInitialState() {
    return {
      show: true
    };
  },
  onClick() {
    this.setState({
      show: !this.state.show
    });
  },
  render() {
    const page = this.state.show ? [
      <div className="demo-header" key="header">
        <div className="logo">
          <img width="30" src="https://t.alipayobjects.com/images/rmsweb/T1B9hfXcdvXXXXXXXX.svg" />
          <span>logo</span>
        </div>
        <QueueAnim component="ul">
          <li key="0"></li>
          <li key="1"></li>
          <li key="2"></li>
          <li key="3"></li>
          <li key="4"></li>
        </QueueAnim>
      </div>,
      <QueueAnim className="demo-content" key="content" delay={300}>
        <div className="demo-title" key="title">我是标题</div>
        <div className="demo-kp" key="b">
          <QueueAnim component="ul">
            <li key="0"></li>
            <li key="1"></li>
            <li key="2"></li>
          </QueueAnim>
        </div>
        <div className="demo-title" key="title2">我是标题</div>
        <div className="demo-listBox">
          <QueueAnim className="demo-list" delay={500}>
            <div className="title" key="title3"></div>
            <QueueAnim component="ul" type="bottom" key="li">
              <li key="0"></li>
              <li key="1"></li>
              <li key="2"></li>
              <li key="3"></li>
              <li key="4"></li>
            </QueueAnim>
          </QueueAnim>
        </div>
      </QueueAnim>,
      <QueueAnim delay={1000} type="bottom" key="footerBox">
        <div className="demo-footer" key="footer"></div>
      </QueueAnim>
    ] : null;
    return (
      <div>
        <p className="buttons">
          <Button type="primary" onClick={this.onClick}>切换</Button>
        </p>
        <QueueAnim type={['right', 'left']}>{page}</QueueAnim>
      </div>
    );
  }
});

ReactDOM.render(<Test />, mountNode);

模拟一个完整的页面。

import { QueueAnim, Button } from 'antd';

const Test = React.createClass({
  getInitialState() {
    return {
      show: true
    };
  },
  onClick() {
    this.setState({
      show: !this.state.show
    });
  },
  render() {
    const list = this.state.show ? [
      <div className="demo-kp" key="a">
        <ul>
          <li></li>
          <li></li>
          <li></li>
        </ul>
      </div>,
      <div className="demo-listBox" key="b">
        <div className="demo-list">
          <div className="title"></div>
          <ul>
            <li></li>
            <li></li>
            <li></li>
          </ul>
        </div>
      </div>
    ] : null;
    return (
      <div>
        <p className="buttons">
          <Button type="primary" onClick={this.onClick}>切换</Button>
        </p>
        <QueueAnim className="demo-content">
          {list}
        </QueueAnim>
      </div>
    );
  }
});

ReactDOM.render(<Test />, mountNode);

同时支持进场和离场动画。

import { QueueAnim, Button } from 'antd';

const Test = React.createClass({
  getInitialState() {
    return {
      show: true,
    };
  },
  onClick() {
    this.setState({
      show: !this.state.show,
    });
  },
  render() {
    const list = this.state.show ? [
      <div className="demo-kp" key="a">
        <ul>
          <li></li>
          <li></li>
          <li></li>
        </ul>
      </div>,
      <div className="demo-listBox" key="b">
        <div className="demo-list">
          <div className="title"></div>
          <ul>
            <li></li>
            <li></li>
            <li></li>
          </ul>
        </div>
      </div>
    ] : null;
    return (
      <div>
        <p className="buttons">
          <Button type="primary" onClick={this.onClick}>切换</Button>
        </p>
        <QueueAnim className="demo-content"
          animConfig={[
            { opacity: [1, 0], translateY: [0, 50] },
            { opacity: [1, 0], translateY: [0, -50] }
          ]}>
          {list}
        </QueueAnim>
      </div>
    );
  }
});

ReactDOM.render(<Test />, mountNode);

通过 animConfig 来自定义动画进出场。

import { QueueAnim, Button } from 'antd';

const Test = React.createClass({
  getInitialState() {
    return {
      show: true,
      items: [
        <li key="0"></li>,
        <li key="1"></li>,
        <li key="2"></li>
      ],
    };
  },
  onClick() {
    this.setState({
      show: !this.state.show,
    });
  },
  onAdd() {
    let items = this.state.items;
    items.push(<li key={Date.now()}></li>);
    this.setState({
      show: true,
      items,
    });
  },
  onRemove() {
    let items = this.state.items;
    items.splice(items.length - 1, 1);
    this.setState({
      show: true,
      items,
    });
  },
  render() {
    return (
      <div>
        <p className="buttons">
          <Button type="primary" onClick={this.onClick}>切换</Button>
          <Button onClick={this.onAdd} style={{ marginLeft: 10 }}>添加</Button>
          <Button onClick={this.onRemove} style={{ marginLeft: 10 }}>删除</Button>
        </p>
        <div className="demo-content">
            <div className="demo-listBox" key="b">
              <div className="demo-list">
                <div className="title"></div>
                <QueueAnim component="ul" type={['right', 'left']}>
                  {this.state.show ? this.state.items : null}
                </QueueAnim>
              </div>
            </div>
        </div>
      </div>
    );
  }
});

ReactDOM.render(<Test />, mountNode);

场景里有增加或删除条目时也会触发动画。

const ReactRouter = require('react-router');
let { Router, Route, Link, hashHistory } = ReactRouter;
import { QueueAnim, Menu } from 'antd';

const App = React.createClass({
  render() {
    const key = this.props.location.pathname;
    const keys = key.replace('/', '') ? [key.replace('/', '')] : ['home'];
    return (
      <div>
        <Menu style={{ marginBottom: 10 }} mode="horizontal" selectedKeys={keys}>
          <Menu.Item key="home">
            <Link to="/">首页</Link>
          </Menu.Item>
          <Menu.Item key="page1">
            <Link to="/page1">Page 1</Link>
          </Menu.Item>
          <Menu.Item key="page2">
            <Link to="/page2">Page 2</Link>
          </Menu.Item>
        </Menu>
        <QueueAnim type={['right', 'left']} className="demo-router-wrap">
          {React.cloneElement(this.props.children || <Home />, { key })}
        </QueueAnim>
      </div>
    );
  }
});

const Home = React.createClass({
  render() {
    return (
      <div className="demo-router-child">
        <QueueAnim className="demo-content">
          <div className="demo-kp" key="a">
            <QueueAnim component="ul">
              <li key="0"></li>
              <li key="1"></li>
              <li key="2"></li>
            </QueueAnim>
          </div>
          <div className="demo-kp" key="b">
            <QueueAnim component="ul">
              <li key="0"></li>
              <li key="1"></li>
              <li key="2"></li>
            </QueueAnim>
          </div>
          <div className="demo-kp" key="c">
            <QueueAnim component="ul">
              <li key="0"></li>
              <li key="1"></li>
              <li key="2"></li>
            </QueueAnim>
          </div>
        </QueueAnim>
      </div>
    );
  }
});

const Page1 = React.createClass({
  render() {
    return (
      <div className="demo-router-child">
        <QueueAnim className="demo-content">
          <div className="demo-kp" key="b">
            <QueueAnim component="ul">
              <li key="0"></li>
              <li key="1"></li>
              <li key="2"></li>
            </QueueAnim>
          </div>
          <div className="demo-listBox">
            <QueueAnim className="demo-list" delay={200}>
              <div className="title" key="title3"></div>
              <QueueAnim component="ul" animConfig={{ opacity: [1, 0], translateY: [0, 30], scale: [1, 0.9] }} key="ul">
                <li key="0"></li>
                <li key="1"></li>
                <li key="2"></li>
              </QueueAnim>
            </QueueAnim>
          </div>
        </QueueAnim>
      </div>
    );
  }
});

const Page2 = React.createClass({
  render() {
    return (
      <div className="demo-router-child">
        <div className="demo-content">
          <div className="demo-listBox">
            <QueueAnim className="demo-list">
              <div className="title" key="title3"></div>
              <QueueAnim component="ul" animConfig={{ opacity: [1, 0], translateY: [0, 30], scale: [1, 0.9] }} key="li">
                <li key="0"></li>
                <li key="1"></li>
                <li key="2"></li>
                <li key="3"></li>
                <li key="4"></li>
                <li key="5"></li>
              </QueueAnim>
            </QueueAnim>
          </div>
        </div>
      </div>
    );
  }
});

ReactDOM.render((
  <Router history={hashHistory}>
    <Route path="/" component={App} ignoreScrollBehavior>
      <Route path="page1" component={Page1} />
      <Route path="page2" component={Page2} />
    </Route>
  </Router>
), mountNode);
#components-queue-anim-demo-router .demo-router-wrap {
  position: relative;
  width: 100%;
  margin: auto;
  height:200px;
  overflow: hidden;
}
#components-queue-anim-demo-router .queue-anim-leaving {
  position: absolute;
  width:100%;
}

router 组合的进场与出场动画。