DEV Community

Lauro Lyra Aguiar
Lauro Lyra Aguiar

Posted on • Updated on

Adicionando máscaras nos inputs HTML (JS vanilla)

[UPDATE] Na época da publicação, o código possuia um erro de funcionalidade que o @vitorluizc apontou. Agradeço demais pela observação, no que corrigi o código.

Olá a todos!
Neste post pretendo ensinar a inserir máscaras nos inputs HTML, sem uso de bibliotecas externas e compatível com a maioria dos frameworks javascript - se não com todos eles.

1. O que é uma máscara?

Uma máscara consiste na estilização das informações inseridas pelo usuário no input. isso facilita a leitura e, em nosso exemplo, dará a certeza de que os dados foram inseridos no tamanho correto.

2. Mas se a lib [INSIRA O NOME DE UMA BIBLIOTECA AQUI] faz esse trabalho pra mim, por quê eu deveria criar uma máscara "na mão"?

É uma pergunta muito interessante, a qual comporta várias respostas.

Em primeiro lugar, vale a pena criar sua própria máscara porque, como veremos abaixo, ela se adapta a diversos tipos de input - e muitas libs comportam somente os campos mais comuns, como CPF ou telefone. É muito difícil achar uma lib que, sozinha, crie as máscaras pra todos os campos desejados.

Em segundo lugar, há sempre um grande debate sobre o uso de lib pra tudo. Eu sempre levo em consideração que menos é mais, por isso evito ao máximo o uso de bibliotecas externas, usando-as somente em casos muito específicos em que ela facilita minha vida com uma funcionalidade de lógica muito complexa ou quando ela já possui uma construção sólida, caso da Yup.

Em terceiro lugar, é uma boa experiência de aprendizado e manipulação de objetos.

3. Passadas essas considerações, mãos à obra!

Vamos começar criando um HTML dando olá ao mundo (não acredito em maldições, mas é melhor não contrariar). Teremos também um campo de input destinado ao CPF do usuário:

<!DOCTYPE html>
<html lang="pt-br">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Mask input</title>
</head>
<body>
  <h1>Hello World</h1>
  <div>
      <label>digite o CPF / insert CPF:</label>
      <input id="CPFInput" maxlength="14">
  </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Atente que o nosso input possui o atributo maxlength. Ele é o grande responsável por limitar o campo para que sejam inseridos os caracteres que o nosso campo usa, bem como eventuais traços e pontos - que serão exibidos posteriormente.

Também criamos uma id para nosso input. Isso será importante para os passos posteriores.

Feito isso, vamos criar a tag script e inserir nossa função dentro do input:

  <h1>Hello World</h1>
  <div>
    <label>digite o CPF / insert CPF:</label>
    <input id="CPFInput" maxlength="14" oninput="criaMascara()">
    </div>
</body>
<script>
  function criaMascara(){
    console.log('algo foi digitado!')
  };
</script>
</html>
Enter fullscreen mode Exit fullscreen mode

(ocultei algumas linhas pra não ficar repetitivo)

Ao implementarmos a função criaMascara, note que, sempre que algo for digitado dentro do input, surgirá no console a frase algo foi digitado!.

Agora vamos apagar esse teste. Nossa manipulação do DOM começa agora.

Vamos começar passando um parâmetro pra nossa função criaMascara - neste caso, será a string 'CPF'. Dentro da função, manipularemos o DOM a partir dessa variável. Observe:

      <input id="CPFInput" maxlength="14" oninput="criaMascara('CPF')">
  </div>
</body>
<script>
  function criaMascara(mascaraInput) {
    const tamanhoInput = document.getElementById(`${mascaraInput}Input`).maxLength;
    let valorInput = document.getElementById(`${mascaraInput}Input`).value;
   console.log('tamanho máximo:', tamanhoInput, 'valor do input:', valorInput)
  };
</script>
Enter fullscreen mode Exit fullscreen mode

Graças ao uso das Template literals, podemos selecionar um elemento do DOM dinamicamente e aplicar esta função para todo o nosso código, bastando dar uma ID com o mesmo padrão da CPFInput - algo como telefoneInput, CNPJInput, CEPInput e etc. E ao digitarmos qualquer coisa, teremos o valor do input e o atributo maxLength do nosso input escrito no console.

Feitos esses passos, vamos criar um objeto que conterá o formato da nossa máscara, a qual será aplicada tão logo o nosso input tiver o tamanho igual ao atributo maxLength:

<script>
  function criaMascara(mascaraInput) {
    const maximoInput = document.getElementById(`${mascaraInput}Input`).maxLength;
    let valorInput = document.getElementById(`${mascaraInput}Input`).value;
    const mascaras = {
      CPF: valorInput.replace(/[^\d]/g, "").replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, "$1.$2.$3-$4")
    };

    if (valorInput.length === maximoInput) {
      (document.getElementById(`${mascaraInput}Input`).value = mascaras[mascaraInput])
    }
  };
</script>
Enter fullscreen mode Exit fullscreen mode

Se você for familiarizado com ternários, pode utilizar essa sintaxe:

<script>
  function criaMascara(mascaraInput) {
    const maximoInput = document.getElementById(`${mascaraInput}Input`).maxLength;
    let valorInput = document.getElementById(`${mascaraInput}Input`).value;
    const mascaras = {
      CPF: valorInput.replace(/[^\d]/g, "").replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, "$1.$2.$3-$4")
    };

    valorInput.length === maximoInput && (document.getElementById(`${mascaraInput}Input`).value = mascaras[mascaraInput]);
  };
</script>
Enter fullscreen mode Exit fullscreen mode

Chamamos a função SEMPRE que o input é alterado, mas, como dissemos acima, ela somente fará algo quando o tamanho do nosso input for igual à propriedade maxLength.

No caso concreto, quando nosso input tiver exatos 11 caracteres, o valor do input será igual à chave CPF da nossa variável mascaras - ou seja, nosso input será afetado por duas funções replace.

[UPDATE] Todavia, precisamos nos certificar que, quando o tamanho do nosso input sem caracteres especiais for menor que o valor máximo, os pontos e traços deverão ser removidos da string. Por isso, tratei de criar uma nova variável chamada valorSemPonto e acrescentei uma nova condição ao nosso ternário (equivalente a um else):

<script>
  function criaMascara(mascaraInput) {
    const maximoInput = document.getElementById(`${mascaraInput}Input`).maxLength;
    let valorInput = document.getElementById(`${mascaraInput}Input`).value;
    let valorSemPonto = document.getElementById(`${mascaraInput}Input`).value.replace(/([^0-9])+/g, "");
    const mascaras = {
      CPF: valorInput.replace(/[^\d]/g, "").replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, "$1.$2.$3-$4")
    };

    valorInput.length === maximoInput ? document.getElementById(`${mascaraInput}Input`).value = mascaras[mascaraInput] : document.getElementById(`${mascaraInput}Input`).value = valorSemPonto;
  };
</script>
Enter fullscreen mode Exit fullscreen mode

a variável valorSemPonto nada mais é do que uma substituição de todos os caracteres não numéricos por nada - na prática, estamos removendo qualquer coisa que não seja um número

Graças às REGExp que implementamos em cada função, o primeiro replace removerá todos os caracteres não numéricos. O segundo aplicará os pontos após cada grupo de três números e, em seguida, um traço antes dos dois últimos números.

E sua máscara está pronta, reaproveitando tudo com apenas uma função e usando um objeto ao invés de um monte de if, reduzindo sobremaneira a complexidade do código!

Agora você pode criar quantos inputs quiser, e no formato que quiser, basta atentar pro nome do id de cada input, atribuir um número máximo de caracteres com maxLength e inserir a REGExp adequada. Abaixo temos o código pronto com mais três exemplos (clique em HTML para ver os demais inputs).

Espero que tenham gostado desta postagem. Até a próxima!

Top comments (6)

Collapse
 
vitorluizc profile image
Vitor Luiz Cavalcanti

Tem um adicional no "2.", uma boa parte das bibliotecas de máscara deixaram de ser mantidas e só acumulam issues abertas. Então ter uma solução própria, mesmo que simples, acaba sendo necessário.

A solução em si é muito prática, da pra separar cada função de formatação bonitinho e só usar o helper de máscaras. Porém, talvez pela simplicidade, tem alguns problemas que afetam experiência do usuário.

O primeiro deles é o maxLength não considerar os caracteres inseridos pelo formatador. No CPF, por exemplo, o maxLength correto seria 14 contando os dois pontos (.) e o hífen (-). Com isso para eu editar o último caractere preciso apagar outros 2.

Image from Gyazo

Outro problema, mais complicado de resolver, é a posição do cursor que é "resetada" quando o attributo value é alterado. O método setSelectionRange do <input /> e do <textarea /> que pode ajudar nisso, mas ele não da suporte a todos os tipos de <input /> e calcular a próxima posição depois da formatação ser aplicada é um pouco dificil.

Image from Gyazo

Eu achei muito bacana o artigo, parabéns.

Collapse
 
laurolyra profile image
Lauro Lyra Aguiar

Finalmente atualizei o código. Muito obrigado pelo toque, irmão!

Collapse
 
laurolyra profile image
Lauro Lyra Aguiar

De fato, isso tem rolado com o JS Vanilla. No React, onde desenvolvi a ideia original, não me deparei com isso. Vou estudar essas implementações e dar os devidos créditos na edição! Obrigado!

Collapse
 
loboweissmann profile image
Henrique Lobo Weissmann (Kico)

Belo post!

Collapse
 
albertassi88 profile image
Ruben Albertassi

Top mano, parabéns.

Collapse
 
hfreitas profile image
Hebert Freitas

Excelente artigo, vai pra coleção de snippets.