简体   繁体   中英

Achieve infinite text spinner, spinning text from bottom to top animation

I'm trying to implement an infinite from bottom to top spinning wheel. So far I got the following: 纺车示例

What's working:

  1. It's spins from bottom to top
  2. It starts with the first string again after reaching the end

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;
}

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>

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>


Update

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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM