ReactJS中的复杂SVG动画

时间:2015-10-16 15:17:18

标签: javascript animation svg reactjs

我正在使用React构建一个动画简介,其中包含三个与动画SVG图标配对的标语。

我选择TweenMax来管理SVG动画,因为它提供了强大的跨浏览器支持。这也允许我在d元素上执行<path>属性的简单morph。我将上述内容与ReactTransitionGroup

结合起来

但是,在尝试使TweenMax和React运行时,我遇到了以下问题:

  1. 在选择组件状态时,componentWillReceiveProps在控制台中以某种方式被调用两次。这意味着,我的动画方法将被调用两次。造成这种情况的原因以及使用Redux存储标记索引在这个用例中是更好的选择吗?
  2. 我现在制作动画的方式看起来很粗糙,例如反复重复this.refsfindDOMNode()。当然必须有更好的方法吗?我的代码结构可以通过哪些方式进行改进?
  3. 我完全被难以接受,并希望被指向正确的方向。

    亲切的问候,杰森

    代码

    html,
    body {
      margin: 0;
      padding: 0;
      width: 100%;
      height: 100%;
    }
    body {
      background: rgb(240, 90, 48);
      font: bold 1em sans-serif;
      color: rgb(255, 255, 255);
      text-align: center;
    }
    .reveal-wrap {
      position: absolute;
      width: 200px;
      height: 200px;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      margin: auto;
      overflow: hidden;
    }
    .reveal-icon {
      width: 100px;
      height: 100px;
      margin: 0 auto 2em auto;
    }
    .reveal-icon svg {
      width: 100%;
      height: 100%;
    }
    .reveal-icon .fill {
      fill: rgb(251, 163, 10);
    }
    .reveal-icon .mask {
      fill: rgb(240, 90, 48);
    }
    .reveal-icon .stroke {
      stroke: rgb(251, 163, 10);
      stroke-linecap: round;
      stroke-width: 5;
    }
    .reveal-text {
      position: absolute;
      width: 100%;
    }
    .switch-locale {
      position: fixed;
      top: 1em;
      left: 1em;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.25/browser.min.js"></script>
    <script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js"></script>
    <script src="https://fb.me/react-with-addons-0.13.3.min.js"></script>
    <div id="react-root"></div>
    
    <script type="text/babel">
    const ReactTransitionGroup = React.addons.TransitionGroup
    
    const UI_TEXT = {
        EN_US: {
            REVEAL: [
                { ID: 0, TEXT: 'Tagline 1' },
                { ID: 1, TEXT: 'Tagline 2' },
                { ID: 2, TEXT: 'Tagline 3' }
            ]
        },
        NL_NL: {
            REVEAL: [
                { ID: 0, TEXT: 'Slagzin 1' },
                { ID: 1, TEXT: 'Slagzin 2' },
                { ID: 2, TEXT: 'Slagzin 3' }    
            ]
        }
    }
    
    class Reveal extends React.Component {
        constructor() {
            super();
            this.state = {
                index: 0,
                locale: 'EN_US'
            }
        }
        nextTagline() {
            this.setState({index: this.state.index + 1})
            console.log('this.state.index @ ' + this.state.index)
        }
        switchLocale() {
            let locale = (this.state.locale === 'EN_US') ? 'NL_NL' : 'EN_US'
            this.setState({locale})
        }
        render() {
            return (
                <ReactTransitionGroup className='reveal-wrap' component='div'>
                    <RevealIcon tag={this.state.index} nextTag={() => this.nextTagline()} />
                    <RevealText tag={this.state.index} locale={this.state.locale} key={this.state.index} />
                    <SwitchLocale switchLocale={() => this.switchLocale()} />
                </ReactTransitionGroup>
            )
        }  
    }
    
    class RevealText extends React.Component {
        fadeIn(callback, delay) {
            TweenLite.fromTo(React.findDOMNode(this), 0.5,
                {
                    y: '100px',
                    opacity: 0
                },
                { 
                    y: 0,
                    delay: delay,
                    opacity: 1,
                    ease: Quad.easeOut,
                    onComplete: callback,
                    onCompleteScope: this
                }
            )
        }
        fadeOut(callback, delay) {
            TweenLite.fromTo(React.findDOMNode(this), 0.5,
                {
                    y: 0,
                    opacity: 1
                },
                { 
                    y: '+=100px',
                    delay: delay,
                    opacity: 0,
                    ease: Quad.easeIn,
                    onComplete: callback,
                    onCompleteScope: this
                }
            )
        }
        componentWillAppear(callback) {
            //console.log('RevealText will appear')
            this.fadeIn(callback, 1)
        }
        componentDidAppear() {  
            //console.log("RevealText did appear")
        }
        componentWillLeave(callback) {  
            this.fadeOut(callback, 0)
        }
        componentDidLeave() {
            //console.log('RevealText did leave')
        }   
        componentWillEnter(callback) {
            this.fadeIn(callback, 1)
        }
        componentDidEnter() {
            //console.log("RevealText did enter")
        }        
        render() {
            return (
                <div className='reveal-text'>
                    { UI_TEXT[this.props.locale].REVEAL[this.props.tag].TEXT }
                </div>
            )
        }
    }
    
    class RevealIcon extends React.Component {
        componentWillAppear(callback) {
            const HAND_1 = [React.findDOMNode(this.refs.HAND_1),
                            React.findDOMNode(this.refs.HAND_1_MASK),
                            React.findDOMNode(this.refs.HAND_1_THUMB)]
    
            const HAND_2 = [React.findDOMNode(this.refs.HAND_2),
                            React.findDOMNode(this.refs.HAND_2_MASK)]
    
            const HAND_3 =  React.findDOMNode(this.refs.HAND_LINES)
    
            const HAND_4 = [React.findDOMNode(this.refs.HAND_LINES_1),
                            React.findDOMNode(this.refs.HAND_LINES_2),
                            React.findDOMNode(this.refs.HAND_LINES_3),
                            React.findDOMNode(this.refs.HAND_LINES_4),
                            React.findDOMNode(this.refs.HAND_LINES_5),
                            React.findDOMNode(this.refs.HAND_LINES_6),
                            React.findDOMNode(this.refs.HAND_LINES_7),
                            React.findDOMNode(this.refs.HAND_LINES_8)]
    
            let anim = new TimelineMax({
                delay: 2,
                onComplete: this.props.nextTag,
                onCompleteScope: this
            })
    
            anim.fromTo(HAND_1, 0.5,
                {
                    y: '-=100px',
                    x: '+=100px',
                    opacity: 0
                },
                {
                    y: 0,
                    x: 0,
                    opacity: 1,
                    ease: Quad.easeOut
                })
                .fromTo(HAND_2, 0.5,
                {
                    y: '-=100px',
                    x: '-=100px',
                    opacity: 0
                },
                {
                    y: 0,
                    x: 0,
                    opacity: 1,
                    ease: Quad.easeOut
                }, '-=0.20')
                .fromTo(HAND_3, 0.75,
                {
                    scaleX: 0.5,
                    scaleY: 0.5,
                    transformOrigin: '50% 50%'
                },
                {   
                    scaleX: 1,
                    scaleY: 1,
                    ease: Quad.easeOut
                })          
                .fromTo(HAND_4, 0.5,
                {
                    opacity: 0,
                },
                {   
                    opacity: 1,
                    ease: Quad.easeOut
                }, '-=0.75')
                .fromTo(HAND_4, 1,
                {
                    'stroke-dasharray': '25px',
                    'stroke-dashoffset': '0px'
                },
                {
                    'stroke-dasharray': '25px',
                    'stroke-dashoffset': '25px',
                    ease: Power3.easeOut
                }, '-=0.75')
                .set({}, {}, '+=1')
    
                // .set is used to lengthen the animation by 1 second
        } 
        componentWillReceiveProps(nextProps) {
            console.log('RevealIcon will receive props', 'nextProps.tag: ' + nextProps.tag)
    
            if(nextProps.tag === 1){
                // Animation code / reference to method here
            } else if (nextProps.tag === 2) {
                // Animation code / reference to method here
            } else if (nextProps.tag === 3) {
                // Animation code / reference to method here
            }
        }
        render() {
            return (
                <div className='reveal-icon' >
                    <svg height="200" width="200" viewBox="0, 0, 200, 200">
                        <path ref="HAND_1" className="fill" d="M146.8,79.9l-55.2,55.2c-1.8,1.8-4.8,1.8-6.7,0c-1.8-1.8-1.8-4.8,0-6.7h0l18.4-18.4l-3.3-3.3
                        l-18.4,18.4c-0.9,0.9-2.1,1.4-3.3,1.4s-2.5-0.5-3.3-1.4c-0.9-0.9-1.4-2.1-1.4-3.3c0-1.3,0.5-2.5,1.4-3.3L93.3,100L90,96.7
                        l-18.4,18.4c-1.8,1.8-4.8,1.8-6.7,0c-1.8-1.8-1.8-4.8,0-6.7l41.8-41.8l-3.3-3.3L61.5,105c-3.7,3.7-3.7,9.7,0,13.4
                        c1.8,1.8,4.3,2.8,6.7,2.8c0.2,0,0.4,0,0.6,0c0,0.2,0,0.4,0,0.6c0,2.5,1,4.9,2.8,6.7c1.8,1.8,4.2,2.8,6.7,2.8c0.2,0,0.4,0,0.6,0
                        c-0.2,2.6,0.7,5.3,2.7,7.3c1.8,1.8,4.3,2.8,6.7,2.8c2.4,0,4.8-0.9,6.7-2.8l55.2-55.2L146.8,79.9z"/>
                        <path ref="HAND_2_MASK" className="mask" d="M138.5,105l-22.7-22.7L83.3,49.8L49.8,83.3l32.5,32.5l22.7,22.7
                        c1.8,1.8,4.3,2.8,6.7,2.8c2.4,0,4.8-0.9,6.7-2.8c2-2,2.9-4.7,2.7-7.3c0.2,0,0.4,0,0.6,0c2.5,0,4.9-1,6.7-2.8
                        c1.8-1.8,2.8-4.2,2.8-6.7c0-0.2,0-0.4,0-0.6c0.2,0,0.4,0,0.6,0c2.4,0,4.8-0.9,6.7-2.8c1.8-1.8,2.8-4.2,2.8-6.7
                        C141.2,109.2,140.2,106.8,138.5,105z"/>
                        <path ref="HAND_2" className="fill" d="M138.5,105L83.3,49.8l-3.3,3.3l55.2,55.2c0.9,0.9,1.4,2.1,1.4,3.3c0,1.3-0.5,2.5-1.4,3.3
                        c-1.8,1.8-4.8,1.8-6.7,0L110,96.7l-3.3,3.3l18.4,18.4c0.9,0.9,1.4,2.1,1.4,3.3c0,1.3-0.5,2.5-1.4,3.3c-0.9,0.9-2.1,1.4-3.3,1.4
                        s-2.5-0.5-3.3-1.4L100,106.7l-3.3,3.3l18.4,18.4c1.8,1.8,1.8,4.8,0,6.7c-1.8,1.8-4.8,1.8-6.7,0L53.2,79.9l-3.3,3.3l55.2,55.2
                        c1.8,1.8,4.3,2.8,6.7,2.8c2.4,0,4.8-0.9,6.7-2.8c2-2,2.9-4.7,2.7-7.3c0.2,0,0.4,0,0.6,0c2.5,0,4.9-1,6.7-2.8
                        c1.8-1.8,2.8-4.2,2.8-6.7c0-0.2,0-0.4,0-0.6c0.2,0,0.4,0,0.6,0c2.4,0,4.8-0.9,6.7-2.8c1.8-1.8,2.8-4.2,2.8-6.7
                        C141.2,109.2,140.2,106.8,138.5,105z"/>
                        <path ref="HAND_1_MASK" className="mask" d="M116.7,49.8l-5,5l-3.3-3.3c-1.8-1.8-4.2-2.8-6.7-2.8c-2.5,0-4.9,1-6.7,2.8
                        L73.2,73.2c-3.7,3.7-3.7,9.7,0,13.4c1.8,1.8,4.3,2.8,6.7,2.8c2.4,0,4.8-0.9,6.7-2.8l20.1-20.1l5-5l8.4-8.4L116.7,49.8z"/>                   
                        <path ref="HAND_1_THUMB" className="fill" d="M116.7,49.8l-5,5l-3.3-3.3l0,0c-1.8-1.8-4.2-2.8-6.7-2.8c-2.5,0-4.9,1-6.7,2.8
                        L73.2,73.2c-3.7,3.7-3.7,9.7,0,13.4c1.8,1.8,4.3,2.8,6.7,2.8c2.4,0,4.8-0.9,6.7-2.8l20.1-20.1l-3.3-3.3L83.3,83.3
                        c-1.8,1.8-4.8,1.8-6.7,0s-1.8-4.8,0-6.7l21.7-21.7c0.9-0.9,2.1-1.4,3.3-1.4c1.3,0,2.5,0.5,3.3,1.4l6.7,6.7l8.4-8.4L116.7,49.8z"/>
                        <g ref="HAND_LINES">
                            <line ref="HAND_LINES_8" className="stroke" x1="32.6" y1="32.6" x2="49.4" y2="49.4"/>
                            <line ref="HAND_LINES_7" className="stroke" x1="4.7" y1="100" x2="28.4" y2="100"/>
                            <line ref="HAND_LINES_6" className="stroke" x1="32.6" y1="167.4" x2="49.4" y2="150.6"/>
                            <line ref="HAND_LINES_5" className="stroke" x1="100" y1="195.3" x2="100" y2="171.6"/>
                            <line ref="HAND_LINES_4" className="stroke" x1="167.4" y1="167.4" x2="150.6" y2="150.6"/>
                            <line ref="HAND_LINES_3" className="stroke" x1="195.3" y1="100" x2="171.6" y2="100"/>
                            <line ref="HAND_LINES_2" className="stroke" x1="167.4" y1="32.6" x2="150.6" y2="49.4"/>
                            <line ref="HAND_LINES_1" className="stroke" x1="100" y1="4.7" x2="100" y2="28.4"/>  
                        </g>                                            
                    </svg>
                </div>
            )
        }
    }
    
    class SwitchLocale extends React.Component {
        render() {
            return (
                <button className='switch-locale' onClick={this.props.switchLocale}>
                    Switch locale
                </button>
            )
        }
    }
    
    React.render(<Reveal/>, document.getElementById('react-root'))
    
    </script>

    在左边,我目前在React创建的动画,在右边,我仍然需要以相同的方式实现的图标。

    Tagline animation Tagline icons

0 个答案:

没有答案