Preencher formulários longos pode ser tedioso! Como designers, podemos aprimorar a experiência focando em componentes individuais em um formulário de várias etapas. Esse padrão de design promove uma maneira mais amigável de capturar dados do usuário e, às vezes, solicita muito deles.
O objetivo com nosso formulário de várias etapas será reduzir o fardo de ter um formulário mais extenso para capturar dados do usuário, garantindo ao mesmo tempo que os dados apropriados sejam enviados.
1. Comece com o HTML
Nossa marcação HTML efetivamente nos dará um layout baseado em guias. Teremos três botões, um formulário e alguns indicadores de status para que o usuário tenha certeza em qual etapa está.
1 2 3
Vou começar com três perguntas, mas você pode estender isso para incluir quantas você preferir. O código JavaScript, no final, é dinâmico, o que significa que você pode facilmente adicionar e remover perguntas adicionais.
2. Estilizando o formulário com CSS
Sem CSS, a abordagem de várias etapas não transmite como esperávamos. Aqui está o CSS que usei para estilizar o HTML.
:root { --color-1: #6366f1; --color-1-hover: #4338ca; --color-2: #06b6d4; --color-2-hover: #0891b2; --text-color: #312e81; --status-btn-bg: #f8fafc; --status-btn-bg-hover: #f1f5f9; } body { background: linear-gradient(to left, var(--color-1), var(--color-2)); } .container { margin: 10rem auto; max-width: 500px; background: white; border-radius: 1rem; padding: 2rem; } .form-input { width: 100%; border: 1px solid #ddd; border-radius: .5rem; box-shadow: inset 0px 1px 2px rgba(0, 0, 0, .1); padding: 1rem; box-sizing: border-box; color: var(--text-color); transition: ease-in-out .3s all; } .form-input::placeholder { color: #cbd5e1; } .form-input:focus { outline: none; border-color: var(--color-1); } .btn:focus-within, .form-input:focus-within { box-shadow: #f8fafc 0px 0px 0px 2px, #c7d2fe 0px 0px 0px 6px, #0000 0px 1px 2px 0px; } textarea.form-input { min-height: 150px; } .btn { border: 0; background: var(--color-1); padding: 1rem; border-radius: 25px; color: white; cursor: pointer; } .btn[disabled] { opacity: .5; pointer-events: none; } .btn:hover { background: var(--color-1-hover); transition: ease-in-out .3s all; } .btn-submit { background-color: var(--color-2); } .btn-submit:hover { background-color: var(--color-2-hover); } .pagination { margin-top: 1rem; display: flex; align-items: center; justify-content: center; } .pagination .btn { width: 100%; text-align: center; margin: 0 6px; } .tab-status { display: flex; align-items: center; } .tab-status span { appearance: none; background: var(--status-btn-bg); border: none; border-radius: 50%; width: 2rem; height: 2rem; margin-right: .5rem; display: flex; align-items: center; justify-content: center; } .tab-status span.active { background-color: var(--color-2); color: white; } .hidden { display: none; }
3. Para o JavaScript
Vamos começar declarando algumas variáveis. Os três primeiros terão como alvo os botões que mencionei anteriormente. Em seguida, direcionaremos os painéis de guias e guias como uma coleção de elementos conhecidos como NodeList em JavaScript. Essa é uma maneira elegante de chamá-lo de Array.
eu criei um isEmpty
função para ajudar a determinar rapidamente se um valor de string de entrada de formulário está vazio ou não.
Por fim, há o currentStep
variável que mudará conforme o Próximo e Anterior botões são clicados.
const previousButton = document.querySelector('#prev') const nextButton = document.querySelector('#next') const submitButton = document.querySelector('#submit') const tabTargets = document.querySelectorAll('.tab') const tabPanels = document.querySelectorAll('.tabpanel') const isEmpty = (str) => !str.trim().length let currentStep = 0
Botões Próximo e Anterior
Nosso Próximo e Anterior botões será como o usuário navega no questionário. Vamos alavancar o currentStep
variável para renderizar a etapa apropriada e a guia ativa dinamicamente. Como ele retorna um valor numérico, podemos direcionar o NodeList dinamicamente.
// Next: Change UI relative to the current step and account for button permissions nextButton.addEventListener('click', (event) => { // Prevent default on links event.preventDefault() // Hide current tab tabPanels[currentStep].classList.add('hidden') tabTargets[currentStep].classList.remove('active') // Show next tab tabPanels[currentStep + 1].classList.remove('hidden') tabTargets[currentStep + 1].classList.add('active') currentStep += 1 })
o Próximo botão, uma vez clicado, sinalizará o HTML/CSS para ficar ocupado, ocultando a guia ativa e o painel de guias e mostrando a seguinte pergunta no assistente.
Vamos estender esta ação para chamar mais algumas funções. Uma função será responsável por atualizar os indicadores de status e a outra validará a resposta do usuário inserida antes que ele possa continuar.
Atualizando o status dinamicamente
Para atualizar o status, precisamos realizar uma operação condicional que verifica o estado do currentStep
variável.
Usando o tabTargets
variável, podemos determinar quantas guias existem com o .length()
método. o length()
O método retorna dinamicamente o número de guias no HTML.
Abaixo, adicionei comentários no código para melhor denotar o que acontece após cada instrução condicional.
function updateStatusDisplay() { // If on the last step, hide the next button and show submit if (currentStep === tabTargets.length - 1) { nextButton.classList.add('hidden') previousButton.classList.remove('hidden') submitButton.classList.remove('hidden') validateEntry() // If it's the first step, hide the previous button } else if (currentStep == 0) { nextButton.classList.remove('hidden') previousButton.classList.add('hidden') submitButton.classList.add('hidden') // In all other instances, display both buttons } else { nextButton.classList.remove('hidden') previousButton.classList.remove('hidden') submitButton.classList.add('hidden') } }
Mostraremos e ocultaremos dinamicamente os controles relativos ao início, meio e fim do assistente de formulário.
Validando a entrada do usuário
Para que um questionário/quiz/formulário de várias etapas funcione corretamente, queremos garantir que os dados sejam enviados adequadamente.
Este tutorial apenas arranha a superfície do que você poderia valide no front-end, mas por enquanto, estamos apenas verificando se existe um valor para cada pergunta.
Para estender essa funcionalidade, você pode verificar critérios adicionais, como o tamanho de uma resposta, se alguma entrada de spam/código pode prejudicar o site e muito mais. Eu também aconselharia adicionar validação do lado do servidor para que nenhum código prejudicial entre em nenhum banco de dados.
function validateEntry() { // Query for the current panel's Textarea input let input = tabPanels[currentStep].querySelector('.form-input') // Start by disabling the continue and submit buttons nextButton.setAttribute('disabled', true) submitButton.setAttribute('disabled', true) // Validate on initial function fire setButtonPermissions(input) // Validate on input input.addEventListener('input', () => setButtonPermissions(input)) // Validate if blurring from input input.addEventListener('blur', () => setButtonPermissions(input)) } function setButtonPermissions(input) { if (isEmpty(input.value)) { nextButton.setAttribute('disabled', true) submitButton.setAttribute('disabled', true) } else { nextButton.removeAttribute('disabled') submitButton.removeAttribute('disabled') }
Eu adicionei duas funções que funcionam juntas para ajudar a validar que um valor está presente para cada pergunta.
Primeiro, validaremos quando a função for chamada inicialmente e, em seguida, adicionaremos algumas funções de ouvinte de eventos para definir permissões de botão dinamicamente à medida que você interage com o formulário.
Isto irá desabilitar ou habilitar o Próximo botão e Enviar botões relativos ao currentStep
valor da variável e veja se há texto presente dentro de cada campo do formulário.
Adicionamos as duas funções ao original nextButton
função e chamá-los após cada clique.
// Next: Change UI relative to the current step and account for button permissions nextButton.addEventListener('click', (event) => { event.preventDefault() // Hide current tab tabPanels[currentStep].classList.add('hidden') tabTargets[currentStep].classList.remove('active') // Show next tab tabPanels[currentStep + 1].classList.remove('hidden') tabTargets[currentStep + 1].classList.add('active') currentStep += 1 validateEntry() updateStatusDisplay() })
Nosso botão anterior se assemelha à lógica de botão a seguir com matemática ligeiramente diferente.
// Previous: Change UI relative to the current step and account for button permissions previousButton.addEventListener('click', (event) => { event.preventDefault() // Hide current tab tabPanels[currentStep].classList.add('hidden') tabTargets[currentStep].classList.remove('active') // Show the previous tab tabPanels[currentStep - 1].classList.remove('hidden') tabTargets[currentStep - 1].classList.add('active') currentStep -= 1 nextButton.removeAttribute('disabled') updateStatusDisplay() })
Não precisamos chamar o validateEntry()
função no botão anterior clique, pois é assumido que já haveria um valor no campo do formulário.
Juntando tudo
Abaixo está o resultado final (confira a aba JS para ver todo o código junto). O código JavaScript poderia ser mais otimizado para reutilização. Ainda assim, é contexto suficiente para ajudá-lo a aprender a construir um formulário simples de navegar, e facilita a vida de um usuário quando se trata de focar em uma pergunta específica e respondê-la de forma simples.