Skip to content

Commit

Permalink
Flexible <Anim> component (#492)
Browse files Browse the repository at this point in the history
* proof of concept, mostly working

* working, forwards and backwards, with order

* new anim component

* fixing tests

* small fix, example slide

* demo slide and step counter fix

* documentation

* le fancier demo

* slightly cleaner

* onAnim callback
  • Loading branch information
elifitch authored and kenwheeler committed Apr 24, 2018
1 parent 42d2b08 commit 5ca4e97
Show file tree
Hide file tree
Showing 11 changed files with 311 additions and 130 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,8 @@ The element tags are the bread and butter of your slide content. Most of these t

This tag does not extend from Base. It's special. Wrapping elements in the appear tag makes them appear/disappear in order in response to navigation.

For best performance, wrap the contents of this tag in a native DOM element like a `<div>` or `<span>`.

|Name|PropType|Description|
|---|---|---|
|order|PropTypes.number| An optional integer starting at 1 for the presentation order of the Appear tags within a slide. If a slide contains ordered and unordered Appear tags, the unordered will show first.
Expand All @@ -555,6 +557,21 @@ This tag does not extend from Base. It's special. Wrapping elements in the appea
|endValue|Proptypes.object|An optional style object that defines the ending, active state of the Appear tag. The default animation is a simple fade-in, so the default `endValue` value is `{ opacity: 1 }`.
|easing|PropTypes.string|An optional victory easing curve for the Appear animation. The various options are documented in the [Victory Animation easing docs](https://formidable.com/open-source/victory/docs/victory-animation/#easing). Default value is `quadInOut`

<a name="anim"></a>
#### Anim

If you want extra flexibility with animated animation, you can use the Anim component instead of Appear. It will let you have multi-step animations for each individual fragment. You can use this to create fancy animated intros, in-slide carousels, and many other fancy things. This tag does not extend from Base. It's special.

For best performance, wrap the contents of this tag in a native DOM element like a `<div>` or `<span>`.

|Name|PropType|Description|
|---|---|---|
|order|PropTypes.number| An optional integer starting at 1 for the presentation order of the Appear tags within a slide. If a slide contains ordered and unordered Appear tags, the unordered will show first.
|transitionDuration|PropTypes.number|A duration (in milliseconds) for the animation. Default value is `300`.
|fromStyle|Proptypes.object|A style object that defines the starting, inactive state of the Anim tag.
|toStyle|Proptypes.array|An array of style objects that define each step in the animation. They will step from one toStyle object to another, until that fragment is finished with its animations.
|easing|PropTypes.string|A victory easing curve for the Appear animation. The various options are documented in the [Victory Animation easing docs](https://formidable.com/open-source/victory/docs/victory-animation/#easing).
|onAnim|PropTypes.fun|This function is called every time the Anim component plays an animation. It'll be called with two arguments, forwards, a boolean indicating if it was stepped forwards or backwards, and the index of the animation that was just played.

<a name="blockquote-quote-and-cite-base"></a>
#### BlockQuote, Quote and Cite (Base)
Expand Down
76 changes: 64 additions & 12 deletions example/src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { Component } from 'react';

import {
Appear, BlockQuote, Cite, CodePane, ComponentPlayground, Deck, Fill,
Anim, Appear, BlockQuote, Cite, CodePane, ComponentPlayground, Deck, Fill,
Heading, Image, Layout, Link, ListItem, List, Markdown, MarkdownSlides, Quote, Slide, SlideSet,
TableBody, TableHeader, TableHeaderItem, TableItem, TableRow, Table, Text, GoToAction
} from '../../src';
Expand Down Expand Up @@ -99,7 +99,7 @@ export default class Presentation extends Component {
lang="jsx"
source={require('raw-loader!../assets/deck.example')}
margin="20px auto"
overflow = "overflow"
overflow="overflow"
/>
</Slide>
<Slide goTo={3}>
Expand All @@ -124,9 +124,61 @@ export default class Presentation extends Component {
</Heading>
</Appear>
</Slide>
<Slide transition={['slide']}>
<Anim
onAnim={(forwards, animIndex) => {
/* eslint-disable */
console.log('forwards ', forwards)
console.log('animIndex ', animIndex)
/* eslint-enable */
}}
fromStyle={{
opacity: 0,
transform: 'translate3d(0px, -100px, 0px) scale(1) rotate(0deg)'
}}
toStyle={[
{
opacity: 1,
transform: 'translate3d(0px, 0px, 0px) scale(1) rotate(0deg)'
},
{
opacity: 1,
transform: 'translate3d(0px, 0px, 0px) scale(1.6) rotate(-15deg)'
},
{
opacity: 1,
transform: 'translate3d(0px, 0px, 0px) scale(0.8) rotate(0deg)'
},
{
opacity: 1,
transform: 'translate3d(0px, -200px, 0px) scale(0.8) rotate(0deg)'
},
{
opacity: 1,
transform: 'translate3d(200px, 0px, 0px) scale(0.8) rotate(0deg)'
},
{
opacity: 1,
transform: 'translate3d(0px, 200px, 0px) scale(0.8) rotate(0deg)'
},
{
opacity: 1,
transform: 'translate3d(-200px, 0px, 0px) scale(0.8) rotate(0deg)'
}
]}
easing={'bounceOut'}
transitionDuration={500}
>
<div>
<Heading size={6} caps fit textColor="secondary">
Flexible<br />animations
</Heading>
</div>
</Anim>
</Slide>
<Slide>
<Heading size={2} textColor="secondary" margin="0.25em">
Mix it up!
Mix it up!
</Heading>
<Heading size={6} textColor="tertiary">
You can even jump to different slides with a standard button or custom component!
Expand Down Expand Up @@ -156,7 +208,7 @@ export default class Presentation extends Component {
)}
/>
</Slide>
<Slide transition={['slide']} bgDarken={0.75} getAppearStep={this.updateSteps}>
<Slide transition={['slide']} bgDarken={0.75} getAnimStep={this.updateSteps}>
<Appear>
<Heading size={1} caps textColor="tertiary">
Can
Expand All @@ -172,9 +224,9 @@ export default class Presentation extends Component {
Steps
</Heading>
</Appear>
<Heading size={1} caps fit textColor="secondary">
Steps: {this.state.steps}
</Heading>
<Heading size={1} caps fit textColor="secondary">
Steps: {this.state.steps}
</Heading>
</Slide>
<Slide transition={['zoom', 'fade']} bgColor="primary">
<Heading caps fit>Flexible Layouts</Heading>
Expand Down Expand Up @@ -251,7 +303,7 @@ const myCode = (is, great) => 'for' + 'sharing';
<Heading size={1} caps fit textColor="tertiary">
Your presentations are interactive
</Heading>
<Interactive/>
<Interactive />
</Slide>
</SlideSet>
<Slide transition={['slide']} bgColor="primary"
Expand All @@ -264,7 +316,7 @@ const myCode = (is, great) => 'for' + 'sharing';
<Table>
<TableHeader>
<TableRow>
<TableHeaderItem/>
<TableHeaderItem />
<TableHeaderItem>2011</TableHeaderItem>
<TableHeaderItem>2013</TableHeaderItem>
<TableHeaderItem>2015</TableHeaderItem>
Expand All @@ -285,13 +337,13 @@ const myCode = (is, great) => 'for' + 'sharing';
</TableRow>
<TableRow>
<TableItem>Pepperoni</TableItem>
<TableItem/>
<TableItem />
<TableItem>50.2%</TableItem>
<TableItem>77.2%</TableItem>
</TableRow>
<TableRow>
<TableItem>Olives</TableItem>
<TableItem/>
<TableItem />
<TableItem>24.9%</TableItem>
<TableItem>55.9%</TableItem>
</TableRow>
Expand All @@ -303,7 +355,7 @@ const myCode = (is, great) => 'for' + 'sharing';
<Heading size={1} caps fit lineHeight={1.5} textColor="primary">
Made with love in Seattle by
</Heading>
<Link href="http://www.formidable.com"><Image width="100%" src={images.logo}/></Link>
<Link href="http://www.formidable.com"><Image width="100%" src={images.logo} /></Link>
</Slide>
</Deck>
);
Expand Down
139 changes: 139 additions & 0 deletions src/components/anim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/* eslint-disable react/no-did-mount-set-state */

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { findDOMNode } from 'react-dom';
import findKey from 'lodash/findKey';
import { connect } from 'react-redux';
import { VictoryAnimation } from 'victory-core';
import { victoryEases } from '../utils/types';

class Anim extends Component {
state = {
activeAnimation: -1
};

componentDidMount() {
const shouldDisableAnimation =
this.props.route.params.indexOf('export') !== -1 ||
this.props.route.params.indexOf('overview') !== -1;

if (shouldDisableAnimation) {
this.setState({ activeAnimation: this.props.toStyle.length - 1 });
return;
}

const order = this.props.order;
const node = findDOMNode(this.fragmentRef);
if (!node.dataset) {
node.dataset = {};
}
node.dataset.order = order;
node.dataset.animCount = this.props.toStyle.length;
}

componentWillReceiveProps(nextProps) {
const shouldDisableAnimation =
nextProps.route.params.indexOf('export') !== -1 ||
nextProps.route.params.indexOf('overview') !== -1;

if (shouldDisableAnimation) {
this.setState({ activeAnimation: this.props.toStyle.length - 1 });
return;
}

const animationStatus = this.getAnimationStatus();
if (animationStatus) {
const nextAnimation = animationStatus.every(a => a === true) ?
animationStatus.length - 1 :
animationStatus.indexOf(false) - 1;
if (this.state.activeAnimation !== nextAnimation) {
const state = nextProps.fragment;
const { slide } = this.props.route;
this.context.stepCounter.setFragments(state.fragments[slide], slide);
if (this.props.onAnim) {
const forward = this.state.activeAnimation < nextAnimation;
this.props.onAnim(forward, nextAnimation);
}
this.setState({
activeAnimation: nextAnimation
});
}
}
}

getAnimationStatus() {
const state = this.props.fragment;
const { slide } = this.props.route;
const fragment = findDOMNode(this.fragmentRef);
const slideHash = parseInt(this.context.slideHash, 10);
const key = findKey(state.fragments[slide], {
id: `${slideHash}-${parseInt(fragment.dataset.fid, 10)}`,
});
if (
slide in state.fragments &&
state.fragments[slide].hasOwnProperty(key)
) {
return state.fragments[slide][key].animations;
}
return null;
}

render() {
const {
children,
fromStyle,
toStyle,
transitionDuration,
easing,
style
} = this.props;
const child = React.Children.only(children);
const tweenData = this.state.activeAnimation === -1 ? fromStyle : toStyle[this.state.activeAnimation];
return (
<VictoryAnimation
data={tweenData}
duration={transitionDuration}
easing={easing}
>
{(tweenStyle) =>
React.cloneElement(child, {
className: `fragment ${child.props.className}`.trim(),
style: { ...child.props.style, ...style, ...tweenStyle },
ref: f => {
this.fragmentRef = f;
}
})}
</VictoryAnimation>
);
}
}

Anim.defaultProps = {
order: 0
};

Anim.propTypes = {
children: PropTypes.node,
easing: PropTypes.oneOf(victoryEases).isRequired,
fragment: PropTypes.object,
fromStyle: PropTypes.object.isRequired,
onAnim: PropTypes.func,
order: PropTypes.number,
route: PropTypes.object,
style: PropTypes.object,
toStyle: PropTypes.arrayOf(PropTypes.object).isRequired,
transitionDuration: PropTypes.number.isRequired
};

Anim.contextTypes = {
export: PropTypes.bool,
overview: PropTypes.bool,
slide: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
slideHash: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
stepCounter: PropTypes.shape({
setFragments: PropTypes.func
})
};

export default connect(state => state)(Anim);
Loading

0 comments on commit 5ca4e97

Please sign in to comment.