Чередуйте слова с эффектом пишущей машинки в JavaScript

Я пытаюсь создать код, который печатает строку в стиле пишущей машинки. После этого он должен стереть последнее слово и ввести первое слово из массива. Затем он должен стереть его через пару секунд и ввести второе слово в том же массиве и так далее. После того, как последнее слово было напечатано, он должен вернуться к началу и снова начать печатать первое слово, сохраняя при этом эффект пишущей машинки.

Этот сценарий должен запускаться при щелчке элемента HTML с идентификатором «btn». Вот что мне удалось придумать:

var i = 0;
var j = 0;
var txt = "To me, you are extremely cool";
var array = [" pretty", " smart", " beautiful", " gorgeous", " cool"];
var speed = 50;

document.getElementById("btn").onclick = function () {
  typeWriter();
  setInterval(rotate, 5000);
};

function typeWriter() {
  if (i < txt.length) {
    document.getElementById("demo").innerHTML += txt.charAt(i);
    i++;
    setTimeout(typeWriter, speed);
  }
}

function rotate() {
  let demo = document.getElementById("demo");
  let words = demo.innerHTML.split(" ");
  words.pop();
  demo.innerHTML = words.join(" ");
  document.getElementById("demo").innerHTML += array[j];
  j = (j + 1) % array.length;
}
<button id = "btn" type = "button">Type</button>
<div id = "demo"></div>

Проблема в том, что я не могу успешно совместить предыдущий код со стертым последним словом и другим, напечатанным с эффектом пишущей машинки. Это просто меняет последнее слово. То, что вы видите, — результат многочасовых тщетных попыток.

Я был бы очень признателен за ваш вклад, прямо или косвенно полезный для моей проблемы. Как вы думаете, можно ли решить эту проблему с помощью CSS?

Обновлено: Вот решение, предоставленное асинхронным ожиданием и слегка измененное мной в соответствии с моими потребностями:

document.getElementById("btn").onclick = function () {
  main();
};

function sleep(ms) {
  return new Promise((r) => setTimeout(r, ms));
}

async function typewrite(txt, el) {
  el.innerText = "";
  for (const char of txt) {
    el.textContent += char;
    await sleep(100);
  }
}

async function typewrite2(txt, el) {
  for (const char of txt) {
    el.textContent = el.textContent.slice(0, -1);
    await sleep(250);
  }
}

async function main() {
  const TAILS = ["pretty", "smart", "beautiful", "gorgeous", "cool"];
  const ROOT_TEXT = "I think you are ";
  const root = document.querySelector(".root");
  const tail = document.querySelector(".tail");
  await typewrite(ROOT_TEXT, root);
  let tailIndex = 0;
  while (true) {
    await typewrite(TAILS[tailIndex], tail);
    await sleep(1000);
    await typewrite2(TAILS[tailIndex], tail);
    await sleep(1000);
    tailIndex = (tailIndex + 1) % TAILS.length;
  }
}
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  
.display {
    box-shadow: 0 0 2px 2px pink;
    padding: 1rem;
    border-radius: 5px;
  }

#btn {
    padding: 30px;
    border-radius: 6px;
    left: 50px;
    position: absolute;
    cursor: pointer;
}
<body>
    <button id = "btn" type = "button">Type</button>
    <div class = "display">
        <span class = "root"></span>
        <span class = "tail"></span>
    </div>
</body>

Какой эффект вы хотите? Может быть, вы можете привести больше примеров, например, gif или несколько ссылок.

Mee 30.03.2023 11:17
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
1
131
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Этот код решает вашу проблему? если не объясните желаемый результат больше, чтобы я мог помочь дальше

var i = 0;
var j = 0;
var x = 0;
var txt = "To me, you are extremely ";
var array = ["cool", "pretty", "smart", "beautiful", "gorgeous"];
var speed = 50;
const demo = document.querySelector('#demo');

document.getElementById("btn").onclick = function () {
  if (demo.innerHTML){
    demo.innerHTML = '';
  }
  typeWriter();
};

function typeWriter() {
  if (i < txt.length) {
    demo.innerHTML += txt.charAt(i);
    i++;
    setTimeout(typeWriter, speed);
  } else {
    if (j != array.length){
        if (x >= array[j].length){
            demo.innerHTML = demo.innerHTML.replace(array[j], '');
            j++
            x = 0;
            setTimeout(typeWriter, speed);
        } else {
            demo.innerHTML += array[j].charAt(x);
            x++
            setTimeout(typeWriter, speed);
        }
    } else {
        j = 0;
        i = 0;
    }
  }
}
<button id = "btn">click</button>
<div id = "demo"></div>

Вот еще одно похожее решение, которое я создал за последние несколько недель, используя requsetAnimationFrame API, оно может вам помочь.

function animateText(el, txtArr, delay) {
    let i = 0,
        currentTxt = 0,
        reverse = false;
    const oneFrame = () => {
        requestAnimationFrame(() => {
            if (!reverse){
                el.textContent += txtArr[currentTxt][i];
                i++;
                if (i == txtArr[currentTxt].length){
                    reverse = true
                }
            } else {
                el.textContent = txtArr[currentTxt].substring(0, i);
                i--;
                if (i < 0){
                    i = 0;
                    reverse = false
                    currentTxt = (currentTxt < txtArr.length -1) ? currentTxt+=1 : 0;
                }
            }
        });
        setTimeout(oneFrame, delay);
    };
    setTimeout(oneFrame, delay);
}

document.querySelector('#btn').addEventListener('click', ()=> {
  animateText(document.querySelector('#demo'), ["cool", "pretty", "smart", "beautiful", "gorgeous"], 100);
});
<button id = "btn">click</button>
<div><span>To me, you are extremely </span><span id = "demo"></span></div>

Ваш второй ответ почти точен, я искренне благодарен mmh4all. Одна деталь, которую я бы изменил, заключается в том, что строка «Для меня ты чрезвычайно» должна быть напечатана буква за буквой. Какой подход вы бы использовали для достижения этого? Я должен вернуться к более легким проектам!

Neil Peña Martínez 30.03.2023 12:06

@NeilPeñaMartínez, вы можете добавить текст в массив для каждого слова или вместо элемента take1 в качестве параметра взять 2 элемента в виде массива, demo1, которые получают «Для меня, вы чрезвычайно» в виде текстового содержимого, и когда это будет сделано, увеличьте массив элементов на единицу и сделайте остальные слова в элементе demo2 без перезапуска «Для меня ты чрезвычайно» с начала снова

mmh4all 30.03.2023 12:11
Ответ принят как подходящий

Чтобы остаться верным вашему примеру, я кое-что изменил. Вместо удаления последнего слова я создал переменную ROOT_TEXT, которую буду использовать для сброса значения innerHTML. У вас уже есть функция для машинописи, поэтому все, что мне нужно было сделать, это обновить новый текст для машинописи и новое место для начала машинописи каждый раз, когда происходит ротация. Надеюсь, это имеет смысл.

var i = 0;
var j = 0;
const ROOT_TEXT = "To me, you are extremely";
var txt = "To me, you are extremely cool";
var array = [" pretty", " smart", " beautiful", " gorgeous", " cool"];
var speed = 50;

document.getElementById("btn").onclick = function () {
  typeWriter();
  setInterval(rotate, 5000);
};

function typeWriter() {
  if (i < txt.length) {
    document.getElementById("demo").innerHTML += txt.charAt(i);
    i++;
    setTimeout(typeWriter, speed);
  }
}

function rotate() {
  let demo = document.getElementById("demo");
  demo.innerHTML = ROOT_TEXT;
  i = ROOT_TEXT.length;
  txt = ROOT_TEXT + array[j];
  typeWriter();
  j = (j + 1) % array.length;
}
<div id = "demo"></div>
<button id = "btn">clicky</button>

Я также хотел добавить пример того, как я могу это сделать. Исходя из Python, вы, вероятно, знакомы с time.sleep и асинхронным кодом. Javascript использует обещания для имитации асинхронного поведения. Будет функция с именем sleep, которая возвращает разрешение промиса после тайм-аута, а внутри асинхронных функций я могу использовать ключевое слово await для остановки кода до тех пор, пока промис не разрешится.

function sleep(ms) {
  return new Promise(r => setTimeout(r, ms));
}

async function typewrite(txt, el) {
  el.innerText = "";
  for (const char of txt) {
    el.textContent += char;
    await sleep(75);
  }
}

async function main() {
  const TAILS = ["pretty", "smart", "beautiful", "gorgeous", "cool"];
  const ROOT_TEXT = "I think you are ";
  const root = document.querySelector(".root");
  const tail = document.querySelector(".tail");
  await typewrite(ROOT_TEXT, root);
  let tailIndex = 0;
  while (true) {
    await typewrite(TAILS[tailIndex], tail);
    await sleep(3000);
    tailIndex = (tailIndex + 1) % TAILS.length;
  }
}

main();
body {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  justify-content: center;
  align-items: center;
}

.display {
  box-shadow: 0 0 2px 2px pink;
  padding: 1rem;
  border-radius: 5px;
}
<div class = "display">
  <span class = "root"></span>
  <span class = "tail"></span>
</div>

Я бы порекомендовал разобраться, почему в большинстве случаев использование let и const лучше, чем var. Одна важная вещь, которая отличается от того, как я написал свой пример, заключается в том, что он более многоразовый, упакованный в функции, которые содержат свою область. И я не использую i или j для представления индексов, вместо этого, когда я использую индекс, я ясно даю понять, к чему он относится. for... of петли очень удобны для чтения. Если бы я хотел улучшить это, я мог бы сделать мигающий курсор в конце набора текста с помощью css. Я мог бы изменить регулярные интервалы на человеческий интервал между вводом времени. Я мог бы сделать так, чтобы конечный текст удалялся на машинке, а не весь сразу. Я мог бы сделать последнее слово светящимся или повернуть цвета с помощью css.

Желаю удачи в вашем путешествии, и, надеюсь, мой пример решил вашу проблему и помог вам понять некоторые другие возможности js! Я тоже пришел из Python, и я люблю js. Возможность поместить код прямо в браузер или на сайт кажется мне волшебством :)

Ух ты! Спасибо за такой подробный и добрый ответ! Ваш код гораздо легче понять, чем мой - я многому научился из него. Красивый результат. Как я мог также удалить каждую букву «хвоста» по букве?

Neil Peña Martínez 30.03.2023 11:57

Цель состоит в том, чтобы заменить содержимое элемента содержащимся в нем текстом, пропуская последнюю букву, пока элемент не станет пустым от текста. Чтобы получить все, кроме последней буквы строки, я мог бы использовать метод slice, доступный для строк. Второй аргумент этого метода — это место окончания сегмента возвращаемой строки. Вместо цикла, который увеличивает индекс вперед, я бы начал свой индекс с длины строки и уменьшал бы его до тех пор, пока не достигну 0, приостанавливая каждую итерацию одним и тем же методом.

async await 30.03.2023 12:22

Спасибо за мастерский ответ, терпение и доброту, сэр. Я буду очень дорожить вашим кодом (из которого, кстати, я могу сказать, что вы пришли из Python!). Я добавил его модифицированную версию к моему вопросу, чтобы вы могли исправить меня, если я добавлю какие-либо излишества или чепуху. Хорошего дня!

Neil Peña Martínez 30.03.2023 12:50

Я рад, что вы нашли ценность в примере! Я рад ответить на любые вопросы. Отличная работа, используя слайс, чтобы обратить эффект пишущей машинки! Я могу предложить три вещи: 1. Использование имени typewriter2 для обратного эффекта может сделать неясным, что происходит в функции main. Я настоятельно рекомендую другое имя для вашей второй функции, которое более четко объясняет, что происходит. 2. Многократное нажатие на кнопку вызывает проблемы. Может быть, вы хотите добавить код, отключающий кнопку после нажатия? 3. Я бы рассмотрел возможность использования addEventListener вместо назначения функции .onclick.

async await 30.03.2023 23:10

Другие вопросы по теме