Tela de login com OTP (one-time password) em JavaScript
Como funciona por trás dos panos a telinha de código de verificação com seis dígitos, incluindo navegação automática entre campos, backspace e colar o código inteiro de uma vez.
Login com OTP é aquele fluxo que praticamente todo mundo já usou: você digita o e-mail, recebe um código de uso único (OTP significa one-time password) e digita esse código numa tela com vários campos separados, um para cada dígito. A parte de gerar o código, mandar por e-mail ou SMS e validar no servidor fica para outro momento. Aqui o foco é só a telinha: fazer os seis campos de input se comportarem do jeito que a gente já está acostumado a ver nesse tipo de tela.
O problema de partida
O HTML de base é simples: uma div com seis inputs dentro, nada além disso. Sem JavaScript, cada input funciona isolado, então dá para digitar vários números no mesmo campo e nada acontece automaticamente. O que falta implementar:
- Ao digitar um número, o foco pula para o próximo campo.
- Ao apertar backspace, o número anterior é apagado e o foco volta.
- Ao colar um código inteiro copiado do e-mail, ele se distribui automaticamente pelos seis campos.
Guardando o estado num array
A lógica inteira gira em torno de uma ideia simples: o usuário digita, isso atualiza um array na memória, e só depois esse array é usado para atualizar o que aparece na tela. Nunca o contrário.
const otp = [1, 2, 3];Cada posição do array corresponde a um dígito do código. Para pegar os seis inputs do HTML:
const inputs = document.querySelectorAll(".otp-area input");Preenchendo os campos a partir do array
A primeira função percorre as seis posições e, para cada uma, verifica se existe um valor correspondente no array. Se existir, preenche o input; se não, deixa vazio:
function updateOtp() {
for (let i = 0; i < 6; i++) {
inputs[i].value = otp[i] !== undefined ? otp[i] : "";
}
if (otp.length < 6) {
inputs[otp.length].focus();
}
}
updateOtp();O detalhe do !== undefined importa porque o número zero, sozinho, é considerado falso em uma verificação simples de if. Sem esse cuidado, um código com zero no meio simplesmente não aparece no campo correspondente.
A parte final da função foca automaticamente no próximo campo vazio, então assim que os três primeiros dígitos são preenchidos, o cursor já fica pronto no quarto campo, sem precisar clicar em nada.
Capturando o que o usuário digita
Cada input recebe um listener de keyup, que dispara para qualquer tecla, número, backspace ou qualquer outra:
inputs.forEach((input) => {
input.addEventListener("keyup", handleInputKey);
});Dentro da função, o primeiro passo é identificar o backspace:
function handleInputKey(event) {
const code = event.code.toLowerCase();
if (code === "backspace") {
otp.pop();
updateOtp();
return;
}
if (code.indexOf("digit") === 0) {
const key = parseInt(event.key);
otp.push(key);
updateOtp();
}
}Backspace remove o último item do array com pop, e a interface é atualizada em seguida. Para identificar um número, a verificação usa event.code, que nos navegadores retorna algo como Digit1, Digit2 etc. Testando se essa string começa com digit, o código separa teclas numéricas de qualquer outra tecla (letras, enter, setas). O valor real do dígito vem de event.key, convertido para inteiro com parseInt.
Depois de identificar um número, ele vai direto para o array com push, e a função updateOtp cuida de exibir tudo e mover o foco.
Colando o código inteiro
Copiar e colar o código recebido por e-mail ou SMS é um comportamento que precisa funcionar, mas só faz sentido no primeiro campo. O evento certo para isso é o paste, que existe apenas em inputs e em elementos com contenteditable:
inputs[0].addEventListener("paste", handlePaste);
function handlePaste(event) {
event.preventDefault();
const pasteContent = event.clipboardData.getData("text").trim();
for (let i = 0; i < pasteContent.length; i++) {
if (i >= 6) break;
const digit = parseInt(pasteContent[i]);
if (digit !== undefined && !Number.isNaN(digit)) {
otp.push(digit);
}
}
updateOtp();
}event.preventDefault() bloqueia o comportamento padrão do navegador, que jogaria todo o texto colado dentro de um único campo. Em vez disso, o conteúdo colado é lido via clipboardData.getData("text"), com um trim() para remover espaços que às vezes vêm junto (é comum um código copiado de um e-mail trazer espaços extras no começo ou no fim).
A partir daí, o código percorre caractere por caractere, ignora qualquer coisa que não seja número (com parseInt mais uma checagem de NaN) e limita a leitura aos seis primeiros caracteres úteis, já que o array só tem seis posições. No fim, updateOtp distribui tudo pelos campos de uma vez.
Juntando as peças
Com essas três funções, os seis campos passam a se comportar como qualquer tela de OTP que a gente já usou: digitar avança o foco, apagar volta o foco e remove o dígito, e colar o código inteiro preenche tudo automaticamente. A partir daqui, o próximo passo natural é a parte de servidor: gerar o código, definir um tempo de expiração, enviar por e-mail e validar quando o usuário confirma. Mas isso já é um assunto à parte, essa telinha aqui já está pronta para receber esse fluxo por cima.
Leia também