Solucionado (ver solução)
Solucionado
(ver solução)
6
respostas

Type Effect JavaScript

Galera me ajudem pfv! To precisando criar um efeito de Type com Java Script puro porém eu preciso que o mesmo efeito seja aplicado para mais de um elemento com textos diferentes.

const textos = [ 'texto 1 aqui', 'texto 2 aqui', 'texto 3 aqui',] ;

const elementos = document.querySelectorAll('.texto');

eu consigo adicionar cada um dos textos no inidice desejado desta forma

elementos.map((e,indice)=>{
   e.innerHTML = textos[indice];
})

Agora o que preciso é criar o efeito de type Effect! Ja tendei separanda as palavras, transformando em array de uma letra, criando looping em todas as letras e nada vai. Se precisarem de mais código pra entender a situação assim q eu pegar o pc mando

6 respostas

Olá André, tudo bem com você?

Acredito que nesse caso não queremos fazer um loop, o objetivo principal é controlar bem as nossas estruturas:

  • Array de Palavras
  • ìndice da palavra

Então, vou deixar aqui um exemplo bem simples


<body>
    <div class="home container">
        <p>Rogério Ceni: <span class="home__skills"></span> <span class="home__cursor">&nbsp;</span></p>
    </div>
    <script src="index.js"></script>
</body>

Html vai ter só a frase dinâmica a ser inserida e um cursor, o css eu vou deixar no final do post

Agora no javascript o que precisamos é simplesmente coordenar o conteúdo em pausas, então iremos utilizar o setTimeout

const skills = [
    "Goleiro",
    "Treinador",
    "Artilheiro",
    "Campeão Mundial"
]

const skillSpan = document.querySelector('.home__skills');

const typingDelayInMS = 300;
const erasingDelayInMS = 150;
const nextTextDelayInMS = 2000;

let skillIndex = 0;
let textCharIndex = 0;
`

Uma definição básica, o array de palavras, variáveis para controlar o tempo, e duas de controle do índice do array e índice da palavra

const type = () => {
    if(textCharIndex < skills[skillIndex].length){
        skillSpan.textContent += skills[skillIndex].charAt(textCharIndex);
        textCharIndex++;
        setTimeout(type, typingDelayInMS)
    } else {
        erase()
    }
}
`

Essa é a função báisca, eu vou primeiro checar se a palavra acabou, caso não irei colocar dentro do span o conteúdo atual dele, mais a posição no indice atual da palavra, irei incrementar o índice para na próxima vez, e adiciono um cronometro para esperar alguns segundos e reinvocar a mesma função

Caso já tenha acabado a palavra irei pedir para limpar:

const erase = () => {
    if(textCharIndex > 0){
        skillSpan.textContent = skills[skillIndex].substring(0, textCharIndex--)
        setTimeout(erase, erasingDelayInMS)
    } else {
        changeWord()
    }
}

Aqui eu vou sempre verificar o tamanho do indice, para quando for igual a 0, eu trocar a palavra, e a lógica é a mesma, só que ao invés de concatenar o texto atual, eu vou trocar pela substring, removendo de 1 palavra em 1

E quando chega a 0 eu posso simplesmente pedir para mudar a palavra:

const changeWord = () => {
    textCharIndex = 0;
    skillIndex++;
    skillSpan.textContent = "";

    if(skillIndex > skills.length - 1)
        skillIndex = 0;

    setTimeout(type, nextTextDelayInMS)
}

Aumento o índice do array, e zero o do carácter, e faço a verificação se já terminou o array de palavras, e após isso eu chamo a função type para começar tudo de novo :)

Agora podemos colocar no final do arquivo:

type()

// Ou para trabalhar com os eventos da DOM

document.addEventListener("DOMContentLoaded", () => setTimeout(type, 1000))

Então aqui a lógica é a mesma das primeiras aulas de lógica de programação, um loop é muito rapido para o que queremos trabalhar, na verdade queremos uma espécie de "recursão" utilizando temporizadores

Conseguiu Compreender?

Abraços e Bons Estudos!

css:

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #0a0a0a;
    color: #eee;
}

.container {
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
}

.container > p {
    font-size: 2.5rem;
    text-align: center;
    letter-spacing: 1px;
}

.home__skills {
    color: #ff1e1e;
    font-style: italic;
}

.home__cursor{
    display: inline-block;
    width: 3px;
    background-color: rgba(226, 0, 0, 1);
    margin-left: 0.25rem;
    animation: blink 1s infinite;
}

.home__cursor.--animation-stop {
    animation: none;
}

@keyframes blink {
    60% {
        background-color: rgba(226, 0, 0, 0.25);
    }

    89% {
        background-color: rgba(226, 0, 0, 0.7);
    }

    100% {
        background-color: rgba(226, 0, 0, 1);
    }
}

Opa chefe beleza? No caso eu precisaria deste recurso (sem a função de deletar) em mais de um elemento ao mesmo tempo, assim:

<body>
    <div class="home container">
        <p>Marcelo Groe: <span class="home__skills"></span> <span class="home__cursor">&nbsp;</span></p>
        <p>Renato Gaúcho: <span class="home__skills"></span> <span class="home__cursor">&nbsp;</span></p>
        <p>Everton Cebolinha: <span class="home__skills"></span> <span class="home__cursor">&nbsp;</span></p>
        <p>Grêmio: <span class="home__skills"></span> <span class="home__cursor">&nbsp;</span></p>
    </div>
    <script src="index.js"></script>
</body>
const skills = [
    "Goleiro",
    "Treinador",
    "Artilheiro",
    "Campeão Mundial"
]

const skillSpan = document.querySelectorAll('.home__skills');

Preciso que a primeira palavra seja adicionada no primeiro indice dos elementos, ou seja, em Marcelo Groe eu receba Goleiro e assim por diante.

Nisto eu preciso que cada palavra do array skills seja adicionada ao índice correspondente na NodeList dos elementos. Com essa adição, eu preciso aplicar o efeito de TypeWriter para cada uma das palavras.

Eu consegui adicionar cada palavra ao indice correspondente com um forEach porém eu preciso que cada palavra tenha o metodo de TypeWriter (que eu tentei com o split do array e com a substring porém ambos não deram certo! Eu preciso exatamente do que você fez só que sendo aplicada para cada uma das palavras :'(

Meu código esta basicamente assim HTML

<p class="b-onde-estou__whatsapp__titulo b-onde-estou--typewrite txt-type">
    <span data-write="b-onde-estou--typewrite"></span>
    <span class="b-onde-estou--cursor"></span>
</p>
<p class="b-onde-estou__whatsapp__titulo b-onde-estou--typewrite txt-type">
    <span data-write="b-onde-estou--typewrite"></span>
    <span class="b-onde-estou--cursor"></span>
</p>
<p class="b-onde-estou__whatsapp__titulo b-onde-estou--typewrite txt-type">
    <span data-write="b-onde-estou--typewrite"></span>
    <span class="b-onde-estou--cursor"></span>
</p>

JS

class EfeitoEscrita {
  constructor(elemento, texto, intervaloGeral = 3000) {
    // this.elemento = Array.from(elemento);
    this.texto = Array.from(texto);
    this.elemento = elemento;
    // this.texto = texto;
    this.textoConteudo = '';
    this.intervaloGeral = parseInt(intervaloGeral, 10);
    this.tags = ['&lth2&gt', '&lt/h2&gt'];
    this.escrever();
  }

  escrever(){

    const ll = this.texto.map(elemento=>{
      this.textoConteudo = elemento.substring(0, this.textoConteudo.length + 1);
      // return this.textoConteudo;
      return this.textoConteudo = elemento.substring(0, this.textoConteudo.length + 1);
    })

    this.elemento.forEach((elemento, indice)=>{
      elemento.innerHTML = `<span class="text-inner">${ll[indice]}</span>`;
    })

    // setTimeout(this.escrever(), 1000);
  }
}
document.addEventListener('DOMContentLoaded', iniciar);

function iniciar() {
  const elemento = document.querySelectorAll('[data-write="b-onde-estou--typewrite"]');
  const texto = [
    'VOCE PODE FALAR COMIGO DIRETO PELO WHATSAPP',
    'OU POSTAR UMA ISSUE NO MEU GITHUB',
    'UU SE PREFERIR ME ENVIE UM E-MAIL',
  ];

  const invervaloGeral = 508;

  new EfeitoEscrita(elemento, texto, invervaloGeral);
}

Algumas coisas deste código ainda não estou utilizando com o this.tags mas o que preciso é fazer o this.texto ter o TypeWriter aplicado :D

solução!

Opa André, tudo bem?

Eu particularmente acredito que utilizando classes podem haver algumas batidas de cabeça em relação ao this, o que provavelmente aconteceu ali no setTimeout comentado

Mas vou pegar os 4 elementos com home__skills e fazer o forEach

        <p>Rogerio Ceni : <span class="home__skills"></span> <span class="home__cursor --"> </span></p>
        <p>Telê Santana : <span class="home__skills"></span> <span class="home__cursor --"> </span></p>
        <p>Luis Fabiano : <span class="home__skills"></span> <span class="home__cursor --"> </span></p>
        <p>Lugano : <span class="home__skills"></span> <span class="home__cursor --"> </span></p>

Como o this é dinâmico dentro do javascript, na função type eu asseguro que ele irá referenciar o meu objeto

class AnimationEffect {
  charIndex = 0;
  constructor(element, text, delay){
    this.element = element;
    this.text = text;
    this.delay = delay;
  }

  type(){
    const that = this;

    if(this.charIndex < this.text.length){
      this.element.textContent += this.text.charAt(this.charIndex);
      this.charIndex++;
      setTimeout( () => that.type(), this.delay)
    } else {
      this.charIndex = 0;
      this.element.textContent = "";
      setTimeout( () => that.type(), 2000)
    } 
  }
}

Dado que vai ser cada um em uma linha também criei um array de tempo para tentar sincronizar um pouco:

const delay = [ 500, 380, 325, 230]

E ai é só fazer o forEach normalmente:

const elementos = document.querySelectorAll('.home__skills')
elementos.forEach( (elemento, index) => {
  new AnimationEffect(elemento, skills[index], delay[index]).type()
})

Daria para fazer a mesma coisa utilizando funções da seguinte maneira:

const type = (base = 0) => (element, text, delay) => {
  if (base < text.length) {
    element.textContent += text.charAt(base);
    base++;
    setTimeout( () => {
        type(base)(element, text, delay)
    }, delay);
  } else {
      element.textContent = "";
      setTimeout( () => {
          type()(element, text, delay)
      }, 2000)
  }
}

elementos.forEach( (element, index) => {
    type()(element, skills[index], delay[index])
})

Abraços e Bons Estudos :)

Cara ficou EXATAMENTE como eu precisava, meu deus do céu ficou PERFEITO!

Essa tua sacada de instanciar o this em uma constante mudou completamente TUDO

Cara eu testei aqui e consegui instanciar um delay único para todos terem o mesmo efeito de digitação, alterei o textContent que no caso eu preciso adiconar uma template string desta forma <span class="text-inner">${this.text.charAt(this.charIndex)}</span> e retirei o eraser que no caso eu não preciso e ficou exatamente o que eu precisava! Muito obrigado também por criar em formato de classe pois eu irei reutilizar este código em outra parte do projeto e conseguirei adicionar as tags que eu quero também.

Inclusive eu renomeei e coloquei o forEach dentro de uma função para atribuí-la a um eventListener depois :D

function EscreverAoScroll(){
  elementos.forEach( (elemento, index) => {

    new EfeitoEscrita(elemento, texto[index], delay).escrever()
  })
}

Sua explicação e sua classe ficou tão clara que em poucos minutos eu adequei ao meu gosto, você sem dúvida é um excelente programador e professor :D

Há 2 semanas estava fazendo e refazendo este código do inicio e sempre parada em uma solução que não era o que eu queria, em 30 linhas você me ajudou a resolver isto, muito obrigado <3

Só uma correção que fiz. Além de adicionar o innerHTML pela classe eu adicionei a classe do span text-inner no elemento dentro do forEach assim o elemento só vai receber a classe de estilo com a função ativada :D Muito obrigado amigo