Reputation: 743
I have a cog icon that I want to spin clockwise 180deg when activated (class="cards__cog cards__cog-active") and then spin another 180deg clockwise to return to its deactivated state (class="cards__cog cards__cog-inactive"). I am doing this with React, reacting to a change in the state when the cog is clicked.
The below works but my problems are:
1) It animates on page load (which makes sense because it has the cards__cog-inactive class but what is the alternative?).
2) It is ugly and there must be an easier way.
Thank you
.cards {
&__cog {
position: absolute;
right: 20px;
top: 20px;
width: 10vh;
cursor: pointer;
&-active {
animation: rotate180 1s ease;
animation-fill-mode: forwards;
}
&-inactive {
animation: rotate180to359to0 1s ease;
animation-fill-mode: forwards;
}
}
}
@keyframes rotate180 {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(180deg);
}
}
@keyframes rotate180to359to0 {
0% {
transform: rotate(180deg);
}
99% {
transform: rotate(359deg);
}
100% {
transform: rotate(0deg);
}
}
Upvotes: 3
Views: 1881
Reputation: 2505
Since you are using React, you may prefer to use a Component. Keeping your use of <button>
and the animation styling, you can separate the persistent button state from the transition state so animation occurs only on click and not on reload.
// Image src https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/Twemoji_1f61d.svg/200px-Twemoji_1f61d.svg.png
const SMILEY_PNG = 'data:image/png;base64,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';
class AnimatedIcon extends React.Component {
constructor() {
super();
this.state = {
active: false,
transitionStyle: {}
};
}
render() {
// On click set new state and add transition styling
const onclk = () => {
this.setState({
active: !this.state.active,
transitionStyle: this.state.active ? {animation: 'rotate-from-180 1s ease'} : {animation: 'rotate-to-180 1s ease'}
});
};
return (
<button onClick={onclk} className={this.state.active ? 'active' : 'inactive'}>
<img src={SMILEY_PNG} style={this.state.transitionStyle} />
ClickMe
</button>
);
}
}
ReactDOM.render( <AnimatedIcon /> , document.getElementById('root'));
button {
margin: 10px;
font-size: 20pt;
color: red;
}
button img {
padding: 10px;
vertical-align: middle;
}
.active {
color: green;
}
.active img {
transform: rotate(180deg);
}
@keyframes rotate-to-180 {
from { transform: rotate(0deg); }
to { transform: rotate(180deg); }
}
@keyframes rotate-from-180 {
from { transform: rotate(180deg); }
to { transform: rotate(360deg); }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Upvotes: 0
Reputation: 272901
If your icon is symmetrical, you can consider 2 elements and transition like this.
var e = document.querySelector('.box');
e.addEventListener('click',function() {
e.classList.toggle('active');
})
.box {
display:inline-block;
margin:20px;
transition:0s .5s;
}
.box > i {
transition:.5s;
color:red;
display:block;
}
.box.active {
transform:scaleX(-1);
}
.box.active i{
transform:rotate(180deg);
}
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css">
<div class="box">
<i class="fas fa-cog fa-7x"></i>
</div>
Upvotes: 5
Reputation: 33044
This is yet another answer. Since I don't have the cog I use a triangle
toggle.addEventListener("click", () => {
cog.className = (cog.className == "" || cog.className == "inactive") ? "active" : "inactive"
});
#cog{
margin:0 auto;
width:108px;
outline:1px solid;
transform-origin: 54px 108px;
}
#triangle{
outline:1px solid;
border:25px solid transparent;
border-bottom:100px solid green;
width:0px;
height:1px;
position:relative;
margin:auto;
}
#triangle::before{
content:"";
width:16px;
height:16px;
background:red;
display:block;
position:absolute;
bottom:-108px;
left:-8px;
border-radius:50%;
}
@keyframes rotate1 {
100% {
transform: rotate(180deg);
}
}
@keyframes rotate2 {
0% {
transform: rotate(180deg);
}
100% {
transform: rotate(360deg);
}
}
#cog.active{animation: rotate1 1s ease;
animation-fill-mode: forwards;}
#cog.inactive{animation: rotate2 1s ease;
animation-fill-mode: forwards;}
<div id="cog" class="">
<div id="triangle"></div>
</div>
<input type="button" value="toggle-class" id="toggle" />
Upvotes: 2
Reputation: 6086
You've mentioned that you're using React. Considering this you could try to move rotation logic from css into component itself. Doing so you won't need additional classes, keyframes
, etc. Only transition
and transform
combo is needed in this scenario.
class Cog extends React.Component {
state = {
isActive: false,
togglesCount: 0
}
get rotationValue () {
return `${this.state.togglesCount * 180}deg`
}
get cogStyle () {
return {
transition: 'transform 1s',
transform: `rotateZ(${this.rotationValue})`
}
}
toggle = () => {
this.setState(s => ({
isActive: !s.isActive,
togglesCount: ++s.togglesCount
}))
}
render() {
return (
<button onClick={this.toggle}>
<i className="fas fa-cog fa-3x" style={this.cogStyle}/>
</button>
)
}
}
ReactDOM.render(<Cog />, document.getElementById('root'))
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css">
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root" />
For a css-only solution you could use transition
and rotateY(-180deg)
hack on a parent element:
.icon {
display: inline-block;
}
.icon__inner {
transition: transform 1s;
}
input:checked + .icon {
transform: rotateY(-180deg);
}
input:checked + .icon .icon__inner {
transform: rotateZ(-180deg);
}
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css">
<input type='checkbox' />
<div class="icon">
<i class="icon__inner fas fa-cog fa-3x"></i>
</div>
Upvotes: 6
Reputation: 64164
You can use transition for one of the movements. But not for both, because then one of them goes backwards.
And you can not use an animation for the initial state, because of you problem number one.
This leaves us with the inactive state being at 360 degrees, so that it can transition from 180 to 360 in the correct way, and an animation to go from 0 to 180 for the change from inactive to active
function change () {
var elem = document.getElementById("test");
elem.classList.toggle('active');
}
.test {
width: 200px;
height: 100px;
border: solid 4px red;
margin: 20px;
transform: rotate(360deg);
transition: transform 1s;
}
.active {
animation: activate 1s;
transform: rotate(180deg);
}
@keyframes activate {
from {transform: rotate(0deg);}
to {transform: rotate(180deg);}
}
<div class="test" id="test">TEST</div>
<button onclick="change();">change</button>
Upvotes: 8