[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>
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>
(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>
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>
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>
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>
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)
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, omaxLength
correto seria14
contando os dois pontos (.
) e o hífen (-
). Com isso para eu editar o último caractere preciso apagar outros 2.Outro problema, mais complicado de resolver, é a posição do cursor que é "resetada" quando o attributo
value
é alterado. O métodosetSelectionRange
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.Eu achei muito bacana o artigo, parabéns.
Finalmente atualizei o código. Muito obrigado pelo toque, irmão!
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!
Belo post!
Top mano, parabéns.
Excelente artigo, vai pra coleção de snippets.