Reputation: 4033
I'm trying to implement an infinite from bottom to top spinning wheel. So far I got the following:
What's working:
What I want/what's incorrect:
I want the animation to be infinite, in other words, there's supposed to be no transition. When the animation reaches its last element, it must not jump back to its first, but smoothly attach it to its bottom to create a ongoing from bottom to top animation.
How can I implement such behavior? To achieve the given effect I used js - do I need some more js or fall back to plain css animation?
class Spinner extends Component {
state = {
active: 0
};
interval;
componentDidMount() {
this.interval = setInterval(() => {
this.setState({active: this.state.active === this.props.content.length - 1 ? 0 : this.state.active + 1});
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
setPosition = () => {
const n = 100 / this.props.content.length;
return n * this.state.active * -1;
};
render() {
const style = {
transform: 'translateY(' + this.setPosition() + '%)'
};
return (
<div className="spinner--list d-inline-block align-self-start">
<ul className="p-0 m-0" style={style}>
{
this.props.content.map((item, index) => {
return (
<li key={index}>
<h1>{item}</h1>
</li>
);
})
}
</ul>
</div>
);
}
}
.spinner--list {
max-height: calc((10px + 2vw) * 1.5);
overflow: hidden;
}
.spinner--list ul {
list-style-type: none;
transition: transform 1s;
will-change: transform;
}
.spinner--container h1 {
font-size: calc(10px + 2vw);
line-height: 1.5;
margin: 0;
}
Upvotes: 3
Views: 2219
Reputation: 7433
Here's a solution without ul
and with automatic adjustment to a variable number of texts you want to spin. The solution uses keyframes
and a little trick/illusion in CSS to create an infinite spinning animation on only two span
-s. After every animation iteration, the text of each span changes using JS. The changed text depends on the data-texts
attribute in your HTML. In your ReactJS code, you can simply feed all the texts you want to spin inside these data-texts
attributes.
const spinnerTexts = document.querySelectorAll('[class^="spinner__text--"]')
const texts = spinnerTexts[0].dataset.texts.split(',')
const textPositions = [0, 1]
function initializeSpinnerTexts() {
spinnerTexts.forEach((spinnerText, index) => {
// Initialize the spinner texts' text
spinnerText.innerText = texts[textPositions[index]]
// Change text after every animation iteration
spinnerText.addEventListener('animationiteration', e => {
e.target.innerText = texts[++textPositions[index] % texts.length]
})
})
}
window.onload = initializeSpinnerTexts
* {
margin: 0;
padding: 0;
}
.spinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
overflow: hidden;
}
.spinner__text {
position: relative;
display: inline-block;
/* width has to be set to be >= largest spinning text width */
width: 50px;
}
.spinner__text--top,
.spinner__text--bottom {
display: inline-block;
animation: 1s ease 1.05s infinite running none;
}
.spinner__text--top {
animation-name: spinTop;
}
/* Bottom text spinner has to be configured so that it is positioned
right at the same position as the top one */
.spinner__text--bottom {
position: absolute;
top: 0;
left: 0;
opacity: 0;
animation-name: spinBottom;
}
@keyframes spinTop {
from { transform: translateY(0%); }
to { transform: translateY(-100%); }
}
@keyframes spinBottom {
from {
opacity: 1;
transform: translateY(100%);
}
to {
opacity: 1;
transform: translateY(0%);
}
}
<div class="spinner">
This is a
<!-- Uses two spans to create an illusion of infinite spinning -->
<div class="spinner__text">
<span class="spinner__text--top" data-texts="test., bug., fail."></span>
<span class="spinner__text--bottom" data-texts="text., bug., fail."></span>
</div>
</div>
You can't add a delay between each animation iteration using keyframes
directly. However, you can adjust the values of the keyframes
and the value of animation-duration
such that it looks like there's a delay in between each iteration; in actuality, you're making it so that nothing is happening during the end of each animation. Here's an example below (it's similar to the above one, but with slight adjustments to animation-duration
and keyframes
.
const spinnerTexts = document.querySelectorAll('[class^="spinner__text--"]')
const texts = spinnerTexts[0].dataset.texts.split(',')
const textPositions = [0, 1]
function initializeSpinnerTexts() {
spinnerTexts.forEach((spinnerText, index) => {
// Initialize the spinner texts' text
spinnerText.innerText = texts[textPositions[index]]
// Change text after every animation iteration
spinnerText.addEventListener('animationiteration', e => {
e.target.innerText = texts[++textPositions[index] % texts.length]
})
})
}
window.onload = initializeSpinnerTexts
* {
margin: 0;
padding: 0;
}
.spinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
overflow: hidden;
}
.spinner__text {
position: relative;
display: inline-block;
width: 50px;
}
.spinner__text--top,
.spinner__text--bottom {
display: inline-block;
/* Changed animation duration */
animation: 5s ease 2s infinite running none;
}
.spinner__text--top {
animation-name: spinTop;
}
.spinner__text--bottom {
position: absolute;
top: 0;
left: 0;
opacity: 0;
animation-name: spinBottom;
}
@keyframes spinTop {
0% { transform: translateY(0%); }
/* Spin finishes after 20% of animation duration (1s) */
20% { transform: translateY(-100%); }
/* Nothing from 20% until 100% of animation duration (4s) */
/* This makes it looks like there's a delay between each animation */
100% { transform: translateY(-100%); }
}
@keyframes spinBottom {
/* Similar to spinTop's logic */
0% {
opacity: 1;
transform: translateY(100%);
}
20% {
opacity: 1;
transform: translateY(0%);
}
100% {
opacity: 1;
transform: translateY(0%);
}
}
<div class="spinner">
This is a
<!-- Uses two spans to create an illusion of infinite spinning -->
<div class="spinner__text">
<span class="spinner__text--top" data-texts="test., bug., fail."></span>
<span class="spinner__text--bottom" data-texts="text., bug., fail."></span>
</div>
</div>
Upvotes: 2
Reputation: 605
CSS solution by @keyframes
body {
color: black;
padding: 0;
margin: 0;
}
.text {
padding: 20px 0;
display: flex;
position: relative;
}
.item-1,
.item-2,
.item-3 {
position: absolute;
padding: 20px 0;
margin: 0;
margin-left: 5px;
animation-duration: 4s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
}
.item-1{
animation-name: anim-1;
}
.item-2{
animation-name: anim-2;
}
.item-3{
animation-name: anim-3;
}
@keyframes anim-1 {
0%, 8.3% { top: -50%; opacity: 0; }
8.3%,25% { top: 0%; opacity: 1; }
33.33%, 100% { top: 50%; opacity: 0; }
}
@keyframes anim-2 {
0%, 33.33% { top: -50%; opacity: 0; }
41.63%, 58.29% { top: 0%; opacity: 1; }
66.66%, 100% { top: 50%; opacity: 0; }
}
@keyframes anim-3 {
0%, 66.66% { top: -50%; opacity: 0; }
74.96%, 91.62% { top: 0%; opacity: 1; }
100% { top: 50%; opacity: 0; }
}
<div class="text">This is a
<div class="items">
<p class="item-1">test.</p>
<p class="item-2">bug.</p>
<p class="item-3">fail.</p>
</div>
</div>
Upvotes: 2