I am using React to build an animated intro that comprises three taglines paired with animated SVG icons.
I opted for TweenMax to manage the SVG animations, due to the solid cross-browser support it offers. This also would allow me to perform a simple morph of the d attribute on <path> elements. I am combining the aforementioned with ReactTransitionGroup
However, I ran into the following problems while trying to make TweenMax and React play nice:
While opting for component state, componentWillReceiveProps is somehow called twice in console. This would mean, my animation method would be called twice. What is causing this and might storing the tag index using Redux be a better option in this use case?
The way that I am animating currently seems very crude, e.g. repeating this.refs and findDOMNode() repeatedly. Surely there must be a better way? In which ways could the structure of my code be improved?
I am completely stumped and would love to be pointed in the right direction.
Kind regards, Jason
Code
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>
On the left, the animation I currently have created in React, on the right, the icons I still have to implement in the same manner.
I'm simply trying to change a hover trigger to a click but for some reason any click function I use does not work?
This my current code that works when you hover...
Current Script Thats Working on hover...
$showSidebar.hover(function() {
$wrapper.stop().animate({ left: "-178px" }, 300);
$sidebarSlider.stop().animate({ width: "452px" }, 300);
$latestTweet.stop().animate({width: "452px", top : "-1px", backgroundColor: "#464848" }, 300);
}, function() {
$wrapper.stop().animate({ left: "0" }, 300);
$sidebarSlider.stop().animate({ width: "274px" }, 300);
$latestTweet.stop().animate({ top : "-1px", width: "274px", backgroundColor: "#464848" }, 300);
});
But see my attempts below to get it work when clicked, but weirdly nothing happens.
My Attempt One
$showSidebar.click(function() {
$wrapper.stop().animate({ left: "-178px" }, 300);
$sidebarSlider.stop().animate({ width: "452px" }, 300);
$latestTweet.stop().animate({width: "452px", top : "-1px", backgroundColor: "#464848" }, 300);
}, function() {
$wrapper.stop().animate({ left: "0" }, 300);
$sidebarSlider.stop().animate({ width: "274px" }, 300);
$latestTweet.stop().animate({ top : "-1px", width: "274px", backgroundColor: "#464848" }, 300);
});
and
My Attempt Two
$showSidebar.on('click', function () {
$wrapper.stop().animate({ left: "-178px" }, 300);
$sidebarSlider.stop().animate({ width: "452px" }, 300);
$latestTweet.stop().animate({width: "452px", top : "-1px", backgroundColor: "#464848" }, 300);
}, function() {
$wrapper.stop().animate({ left: "0" }, 300);
$sidebarSlider.stop().animate({ width: "274px" }, 300);
$latestTweet.stop().animate({ top : "-1px", width: "274px", backgroundColor: "#464848" }, 300);
});
and a couple of others but nudda...
This is my mark up...
<span>//</span> MORE
and my var
$showSidebar = $("#show-sidebar");
Any ideas where I'm going wrong? Would be hugely helpful thanks!
See working code, thanks #hurshagrawal
$showSidebar.on('click', function () {
if ($showSidebar.html() == '<span>//</span> CLOSE') {
$wrapper.stop().animate({ left: "0" }, 300);
$sidebarSlider.stop().animate({ width: "274px" }, 300);
$latestTweet.stop().animate({ top : "-1px", width: "274px", backgroundColor: "#464848" }, 300);
} else {
$wrapper.stop().animate({ left: "-178px" }, 300);
$sidebarSlider.stop().animate({ width: "452px" }, 300);
$latestTweet.stop().animate({width: "452px", top : "-1px", backgroundColor: "#464848" }, 300);
$showSidebar.html('<span>//</span> CLOSE');
}
});
I think you are looking for the .toggle() command:
$showSidebar.toggle(function() {
$wrapper.stop().animate({ left: "-178px" }, 300);
$sidebarSlider.stop().animate({ width: "452px" }, 300);
$latestTweet.stop().animate({width: "452px", top : "-1px", backgroundColor: "#464848" }, 300);
}, function() {
$wrapper.stop().animate({ left: "0" }, 300);
$sidebarSlider.stop().animate({ width: "274px" }, 300);
$latestTweet.stop().animate({ top : "-1px", width: "274px", backgroundColor: "#464848" }, 300);
});
I don't think $.click() takes 2 functions as parameters. Did you try taking the second function out? If you're trying to toggle between the two, you might have to write something with an if-else in there.
EDIT: Ah, or use .toggle().
Try using bind(), I've found that more reliable across browsers.
http://api.jquery.com/bind/
basic usage in your case would be (untested):
$showSidebar.bind('click', function() {
// do stuff
});