Todas as atividades
Jogo e Combinação

Arrastar e Soltar

O aprendiz arrasta itens para zonas de destino correspondentes. Ideal para classificação, organização e associação de conceitos com categorias.

Conhecimento e aplicação arrastar soltar classificação drag

Experimente

Classifique: Avaliação Formativa ou Somativa?

Clique num item e depois na categoria, ou arraste. Pode arrastar de volta para a lista para mudar de ideia.

Sondagem antes do módulo
Certificação de conclusão
Prova final com nota oficial
Quiz de verificação ao fim de uma aula

Formativa

Somativa

Código-fonte

PHP

Dados de exemplo

Cada item carrega a categoria correta; a lista é embaralhada só na exibição.

1 <?php
2 $instrucao = 'Arraste cada palavra para a categoria certa:';
3 $categorias = [
4 ['id' => 'vertebrado', 'nome' => 'Vertebrado'],
5 ['id' => 'invertebrado', 'nome' => 'Invertebrado'],
6 ];
7 $itens = [
8 ['id' => 'i1', 'texto' => 'Sapo', 'correta' => 'vertebrado'],
9 ['id' => 'i2', 'texto' => 'Minhoca', 'correta' => 'invertebrado'],
10 ['id' => 'i3', 'texto' => 'Tubarão', 'correta' => 'vertebrado'],
11 ['id' => 'i4', 'texto' => 'Abelha', 'correta' => 'invertebrado'],
12 ];
13 shuffle($itens);
14 ?>

Estrutura da questão

Banco de itens arrastáveis acima, zonas de destino abaixo — cada item pode ir e voltar livremente até o aluno clicar em Verificar.

1 <div class="questao" data-tipo="arrastar-soltar">
2
3 <p class="questao-instrucao"><?= htmlspecialchars($instrucao) ?></p>
4
5 <div class="banco">
6 <?php foreach ($itens as $item): ?>
7 <div class="item" draggable="true" data-item-id="<?= $item['id'] ?>" data-correta="<?= $item['correta'] ?>">
8 <?= htmlspecialchars($item['texto']) ?>
9 </div>
10 <?php endforeach; ?>
11 </div>
12
13 <div class="zonas">
14 <?php foreach ($categorias as $cat): ?>
15 <div class="zona" data-zona-id="<?= $cat['id'] ?>" data-zona-label="<?= htmlspecialchars($cat['nome']) ?>">
16 <p class="zona-titulo"><?= htmlspecialchars($cat['nome']) ?></p>
17 <div class="zona-itens"></div>
18 </div>
19 <?php endforeach; ?>
20 </div>

Ações da questão

Verificar, o painel de resultado e o botão de reiniciar — os dois últimos nascem ocultos.

1 <button type="button" class="questao-verificar">Verificar</button>
2
3 <div class="questao-painel" hidden>
4 <p class="questao-painel-mensagem"></p>
5 </div>
6
7 <button type="button" class="questao-reset" hidden>Tentar novamente</button>
8
9 </div>
CSS

Banco de itens

Bandeja de onde os itens saem e para onde podem voltar.

1 .banco {
2 display: flex;
3 flex-wrap: wrap;
4 gap: 8px;
5 min-height: 44px;
6 margin-bottom: 20px;
7 padding: 12px;
8 border: 1px solid #cbd5e1;
9 border-radius: 12px;
10 background: #f8fafc;
11 }

Item arrastável

Cursor de "pegar", área grande o bastante pro toque.

1 .item {
2 padding: 8px 14px;
3 border: 1px solid #94a3b8;
4 border-radius: 8px;
5 background: #e2e8f0;
6 font-size: 14px;
7 cursor: grab;
8 user-select: none;
9 }

Estados de interação

Seleção neutra (aguardando clique na zona) e arraste em andamento.

1 .item.selecionada {
2 outline: 2px solid #6366f1;
3 }
4
5 .item.arrastando {
6 opacity: 0.5;
7 }

Zonas de destino

Borda tracejada até receber algo; realce ao arrastar por cima (o mesmo realce vale para o banco, quando o item volta).

1 .zonas {
2 display: grid;
3 grid-template-columns: 1fr 1fr;
4 gap: 12px;
5 margin-bottom: 20px;
6 }
7
8 .zona {
9 min-height: 80px;
10 padding: 12px;
11 border: 2px dashed #cbd5e1;
12 border-radius: 12px;
13 background: #f8fafc;
14 }
15
16 .zona-titulo {
17 margin: 0 0 8px;
18 font-size: 11px;
19 font-weight: 700;
20 letter-spacing: 0.05em;
21 text-transform: uppercase;
22 color: #64748b;
23 }
24
25 .zona-itens {
26 display: flex;
27 flex-wrap: wrap;
28 gap: 8px;
29 }
30
31 .zona.alvo,
32 .banco.alvo {
33 border-color: #818cf8;
34 }
35
36 .zona.preenchida {
37 border-style: solid;
38 }

Resultado da verificação

Verde para quem foi parar na categoria certa, vermelho para quem errou ou ficou esquecido no banco.

1 .item.correta {
2 border-color: #22c55e;
3 background: rgba(34, 197, 94, 0.15);
4 }
5
6 .item.errada {
7 border-color: #ef4444;
8 background: rgba(239, 68, 68, 0.15);
9 }

Ações

Botões de largura total, um de cada vez na tela.

1 .questao-verificar,
2 .questao-reset {
3 display: block;
4 width: 100%;
5 padding: 12px 16px;
6 border: none;
7 border-radius: 10px;
8 font: inherit;
9 font-weight: 600;
10 cursor: pointer;
11 }
12
13 .questao-verificar {
14 background: #4f46e5;
15 color: #fff;
16 }
17
18 .questao-verificar:disabled {
19 opacity: 0.6;
20 cursor: default;
21 }
22
23 .questao-reset {
24 background: #e5e7eb;
25 color: #1f2937;
26 }

Estado oculto

O atributo hidden nativo tem a mesma especificidade da regra de exibição acima — sem isto, "Tentar novamente" apareceria antes de verificar.

1 .questao-verificar[hidden],
2 .questao-reset[hidden] {
3 display: none;
4 }

Painel de resultado

Caixa de destaque com a contagem de acertos, abaixo das zonas.

1 .questao-painel {
2 margin: 16px 0;
3 padding: 16px;
4 border-radius: 12px;
5 background: #1e3a8a;
6 color: #dbeafe;
7 font-weight: 600;
8 }
JavaScript

Encontrar os elementos da questão

Localiza o banco de itens, as zonas de destino e as ações.

1 document.addEventListener('DOMContentLoaded', function () {
2 document.querySelectorAll('.questao[data-tipo="arrastar-soltar"]').forEach(function (questao) {
3 var banco = questao.querySelector('.banco');
4 var zonas = questao.querySelectorAll('.zona');
5 var verificar = questao.querySelector('.questao-verificar');
6 var painel = questao.querySelector('.questao-painel');
7 var reset = questao.querySelector('.questao-reset');
8 // O item selecionado por clique, aguardando o clique numa zona (ou no banco, para desfazer).
9 var selecionado = null;
10
11 function limparSelecao() {
12 if (selecionado) selecionado.classList.remove('selecionada');
13 selecionado = null;
14 }

Mover um item

Um único ponto de movimentação — troca o container do item (zona ou banco de volta) e mantém a marcação de "zona preenchida" em dia dos dois lados.

1 function atualizarZona(zona) {
2 zona.classList.toggle('preenchida', !!zona.querySelector('.item'));
3 }
4
5 function mover(item, destino) {
6 var zonaAnterior = item.closest('.zona');
7 if (destino.classList.contains('zona')) {
8 destino.querySelector('.zona-itens').appendChild(item);
9 atualizarZona(destino);
10 } else {
11 destino.appendChild(item);
12 }
13 if (zonaAnterior && zonaAnterior !== destino) atualizarZona(zonaAnterior);
14 item.classList.remove('correta', 'errada');
15 limparSelecao();
16 }

Selecionar e arrastar

Clicar seleciona (clicar de novo desfaz); o stopPropagation impede que o clique "vaze" para a zona ou o banco por baixo do item e mova ele sozinho de volta.

1 banco.querySelectorAll('.item').forEach(function (item) {
2 item.addEventListener('click', function (evento) {
3 evento.stopPropagation();
4 if (verificar.disabled) return;
5 if (selecionado === item) { limparSelecao(); return; }
6 limparSelecao();
7 selecionado = item;
8 item.classList.add('selecionada');
9 });
10 item.addEventListener('dragstart', function (evento) {
11 if (verificar.disabled) { evento.preventDefault(); return; }
12 selecionado = item;
13 item.classList.add('arrastando');
14 evento.dataTransfer.setData('text/plain', item.dataset.itemId);
15 });
16 item.addEventListener('dragend', function () {
17 item.classList.remove('arrastando');
18 limparSelecao();
19 });
20 });

Zonas de destino

Clique (com um item selecionado) ou soltar via arraste têm o mesmo efeito: mover o item pra dentro da zona.

1 zonas.forEach(function (zona) {
2 zona.addEventListener('click', function () { if (selecionado && !verificar.disabled) mover(selecionado, zona); });
3 zona.addEventListener('dragover', function (evento) { evento.preventDefault(); zona.classList.add('alvo'); });
4 zona.addEventListener('dragleave', function () { zona.classList.remove('alvo'); });
5 zona.addEventListener('drop', function (evento) {
6 evento.preventDefault();
7 zona.classList.remove('alvo');
8 var item = questao.querySelector('[data-item-id="' + evento.dataTransfer.getData('text/plain') + '"]');
9 if (item) mover(item, zona);
10 });
11 });

Devolver ao banco

O banco também é um alvo válido de arraste (ou clique) — devolver um item aqui é como o aluno desfaz uma escolha antes de verificar.

1 banco.addEventListener('click', function () { if (selecionado && !verificar.disabled) mover(selecionado, banco); });
2 banco.addEventListener('dragover', function (evento) { evento.preventDefault(); banco.classList.add('alvo'); });
3 banco.addEventListener('dragleave', function () { banco.classList.remove('alvo'); });
4 banco.addEventListener('drop', function (evento) {
5 evento.preventDefault();
6 banco.classList.remove('alvo');
7 var item = questao.querySelector('[data-item-id="' + evento.dataTransfer.getData('text/plain') + '"]');
8 if (item) mover(item, banco);
9 });

Verificar

Todo item conta — inclusive quem ficou esquecido no banco, que nunca é marcado como certo.

1 verificar.addEventListener('click', function () {
2 limparSelecao();
3 var todosItens = questao.querySelectorAll('.item');
4 var acertos = 0;
5 todosItens.forEach(function (item) {
6 var zona = item.closest('.zona');
7 var correto = !!zona && item.dataset.correta === zona.dataset.zonaId;
8 item.classList.add(correto ? 'correta' : 'errada');
9 if (correto) acertos++;
10 });
11
12 painel.querySelector('.questao-painel-mensagem').textContent = acertos === todosItens.length
13 ? 'Correto! Todos os itens na categoria certa.'
14 : acertos + ' de ' + todosItens.length + ' corretos.';
15 painel.hidden = false;
16
17 verificar.disabled = true;
18 verificar.hidden = true;
19 reset.hidden = false;
20 });

Reiniciar

Devolve todo mundo ao banco e limpa os estados, inclusive de zonas que ficaram marcadas como preenchidas.

1 reset.addEventListener('click', function () {
2 questao.querySelectorAll('.item').forEach(function (item) {
3 item.classList.remove('selecionada', 'arrastando', 'correta', 'errada');
4 banco.appendChild(item);
5 });
6 zonas.forEach(function (zona) { zona.classList.remove('preenchida', 'alvo'); });
7 painel.hidden = true;
8 verificar.disabled = false;
9 verificar.hidden = false;
10 reset.hidden = true;
11 });