Todas as atividades
Jogo e Combinação

Quebra-Cabeça de Imagem

Uma imagem é dividida em peças embaralhadas que o aprendiz reordena arrastando. O processo de reconstrução cria familiaridade visual com o diagrama, mapa ou conceito representado.

Conhecimento quebra-cabeça imagem drag puzzle

Experimente

Monte o diagrama do Ciclo da Água

A imagem mostra o ciclo completo da água — evaporação, condensação, chuva e escoamento de volta ao mar. Reconstrua a paisagem na posição certa.

Clique numa peça e depois no quadro, ou arraste. Pode arrastar de volta para a lista de peças para mudar de ideia.

Peças

Código-fonte

PHP

Configuração da grade

Uma imagem só, cortada visualmente por CSS — sem gerar um arquivo por peça. cols x rows definem o tamanho da grade e o total de peças.

1 <?php
2 $imagem = 'diagrama.jpg';
3 $cols = 3;
4 $rows = 2;
5 $total = $cols * $rows;
6
7 $posicoes = range(0, $total - 1);
8 shuffle($posicoes);
9
10 // O número do selo é embaralhado à parte da posição correta — se fosse
11 // sempre "posição + 1", dava pra resolver só combinando números, sem
12 // olhar o conteúdo da imagem.
13 $rotulos = range(1, $total);
14 shuffle($rotulos);
15 ?>

Grade de destino

Uma célula vazia por peça, mostrando o número da posição como marca d'água até ser preenchida.

1 <div class="questao" data-tipo="quebra-cabeca-imagem">
2
3 <div class="grade" style="grid-template-columns: repeat(<?= $cols ?>, 1fr);">
4 <?php for ($i = 0; $i < $total; $i++): ?>
5 <div class="slot" data-pos="<?= $i + 1 ?>"></div>
6 <?php endfor; ?>
7 </div>

Peças embaralhadas

Cada peça mostra o mesmo arquivo inteiro, com background-size ampliado pra grade toda e background-position deslocado pra sua fatia — o CSS corta, nenhum arquivo por peça.

1 <div class="pecas" style="grid-template-columns: repeat(<?= $cols ?>, 1fr);">
2 <?php foreach ($posicoes as $pos):
3 $col = $pos % $cols;
4 $row = intdiv($pos, $cols);
5 $bgX = $cols > 1 ? round($col / ($cols - 1) * 100, 2) : 0;
6 $bgY = $rows > 1 ? round($row / ($rows - 1) * 100, 2) : 0;
7 $rotulo = $rotulos[$pos];
8 ?>
9 <button type="button" class="peca" draggable="true" data-pos-correta="<?= $pos ?>"
10 style="background-image: url('<?= $imagem ?>'); background-size: <?= $cols * 100 ?>% <?= $rows * 100 ?>%; background-position: <?= $bgX ?>% <?= $bgY ?>%;">
11 <span class="peca-selo"><?= $rotulo ?></span>
12 </button>
13 <?php endforeach; ?>
14 </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 resposta</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

Grade de destino

Uma célula por peça, com o número da posição como marca d'água até ser preenchida.

1 .grade {
2 display: grid;
3 gap: 4px;
4 margin-bottom: 16px;
5 }
6
7 .slot {
8 position: relative;
9 aspect-ratio: 1 / 1;
10 border: 2px dashed #cbd5e1;
11 border-radius: 8px;
12 background: #f8fafc;
13 }
14
15 .slot::before {
16 content: attr(data-pos);
17 position: absolute;
18 top: 50%;
19 left: 50%;
20 transform: translate(-50%, -50%);
21 font-size: 1.25rem;
22 font-weight: 700;
23 color: #94a3b8;
24 }
25
26 .slot.preenchido {
27 border-style: solid;
28 border-color: #6366f1;
29 }
30
31 .slot.preenchido::before {
32 content: none;
33 }

Bandeja de peças

Mesma grade da área de destino, pra cada peça sair do tamanho certo tanto na bandeja quanto encaixada.

1 .pecas {
2 display: grid;
3 gap: 4px;
4 margin-bottom: 16px;
5 padding: 12px;
6 border: 1px solid #cbd5e1;
7 border-radius: 12px;
8 background: #f8fafc;
9 }

Peça

Botão sem conteúdo de texto — a imagem vem inteira de background-image, cortada por background-size/position calculados no PHP.

1 .peca {
2 position: relative;
3 display: block;
4 width: 100%;
5 aspect-ratio: 1 / 1;
6 border: 2px solid transparent;
7 border-radius: 8px;
8 background-repeat: no-repeat;
9 background-color: #e2e8f0;
10 cursor: grab;
11 }
12
13 .peca.selecionada {
14 border-color: #6366f1;
15 outline: 2px solid #6366f1;
16 }

Selo numérico

Número fixo no canto — embaralhado à parte da posição correta da peça, de propósito, pra não entregar a resposta.

1 .peca-selo {
2 position: absolute;
3 top: 4px;
4 left: 4px;
5 width: 20px;
6 height: 20px;
7 border-radius: 999px;
8 background: rgba(15, 23, 42, 0.7);
9 color: #fff;
10 font-size: 11px;
11 font-weight: 700;
12 display: flex;
13 align-items: center;
14 justify-content: center;
15 }

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-reset {
19 background: #e5e7eb;
20 color: #1f2937;
21 }

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 peças.

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 a grade de destino, a bandeja de peças e as ações.

1 document.addEventListener('DOMContentLoaded', function () {
2 document.querySelectorAll('.questao[data-tipo="quebra-cabeca-imagem"]').forEach(function (questao) {
3 var slots = questao.querySelectorAll('.slot');
4 var pecasDiv = questao.querySelector('.pecas');
5 var verificar = questao.querySelector('.questao-verificar');
6 var painel = questao.querySelector('.questao-painel');
7 var reset = questao.querySelector('.questao-reset');
8 var total = slots.length;
9 // A peça selecionada por clique, aguardando o clique no destino.
10 var selecionada = null;
11
12 function pecaDaPosicao(i) {
13 return questao.querySelector('.peca[data-pos-correta="' + i + '"]');
14 }

Mover uma peça

Um único ponto de movimentação — troca o container da peça (slot ou bandeja) e devolve o slot antigo ao estado vazio.

1 function liberar(peca) {
2 var slotAntigo = peca.parentElement;
3 if (slotAntigo && slotAntigo.classList.contains('slot')) {
4 slotAntigo.classList.remove('preenchido');
5 }
6 }
7
8 function mover(peca, destino) {
9 var ehSlot = destino.classList.contains('slot');
10 if (ehSlot) {
11 var ocupante = destino.querySelector('.peca');
12 if (ocupante && ocupante !== peca) pecasDiv.appendChild(ocupante);
13 }
14 liberar(peca);
15 destino.appendChild(peca);
16 peca.classList.remove('selecionada');
17 if (ehSlot) destino.classList.add('preenchido');
18 selecionada = null;
19 }

Selecionar e arrastar

Clicar seleciona a peça (o stopPropagation evita que o clique vaze pro slot ou pra bandeja por baixo e mova ela sozinha); arrastar funciona em paralelo, terminando na mesma função de mover.

1 questao.querySelectorAll('.peca').forEach(function (peca) {
2 peca.addEventListener('click', function (evento) {
3 evento.stopPropagation();
4 if (selecionada === peca) { peca.classList.remove('selecionada'); selecionada = null; return; }
5 if (selecionada) selecionada.classList.remove('selecionada');
6 selecionada = peca;
7 peca.classList.add('selecionada');
8 });
9 peca.addEventListener('dragstart', function (evento) {
10 selecionada = peca;
11 evento.dataTransfer.setData('text/plain', peca.dataset.posCorreta);
12 });
13 });

Encaixar numa posição

Clique (com peça selecionada) ou soltar via arraste têm o mesmo efeito.

1 slots.forEach(function (slot) {
2 slot.addEventListener('click', function () { if (selecionada) mover(selecionada, slot); });
3 slot.addEventListener('dragover', function (evento) { evento.preventDefault(); });
4 slot.addEventListener('drop', function (evento) {
5 evento.preventDefault();
6 var peca = pecaDaPosicao(evento.dataTransfer.getData('text/plain'));
7 if (peca) mover(peca, slot);
8 });
9 });

Devolver à bandeja

A bandeja também é um alvo válido — devolver uma peça aqui é como o aluno desfaz uma escolha antes de verificar.

1 pecasDiv.addEventListener('click', function () { if (selecionada) mover(selecionada, pecasDiv); });
2 pecasDiv.addEventListener('dragover', function (evento) { evento.preventDefault(); });
3 pecasDiv.addEventListener('drop', function (evento) {
4 evento.preventDefault();
5 var peca = pecaDaPosicao(evento.dataTransfer.getData('text/plain'));
6 if (peca) mover(peca, pecasDiv);
7 });

Verificar

Uma peça está certa quando sua posição correta bate com o índice do slot em que ela está.

1 verificar.addEventListener('click', function () {
2 var acertos = 0;
3 slots.forEach(function (slot, i) {
4 var peca = slot.querySelector('.peca');
5 if (peca && parseInt(peca.dataset.posCorreta) === i) acertos++;
6 });
7
8 painel.querySelector('.questao-painel-mensagem').textContent = acertos === total
9 ? 'Correto! Imagem montada.'
10 : acertos + ' de ' + total + ' peças no lugar certo.';
11 painel.hidden = false;
12
13 verificar.hidden = true;
14 reset.hidden = false;
15 });

Reiniciar

Devolve as peças à bandeja em ordem embaralhada de novo — sem isto, elas voltariam na ordem em que estavam no DOM, que pode ser a própria ordem resolvida.

1 reset.addEventListener('click', function () {
2 var pecas = Array.prototype.slice.call(questao.querySelectorAll('.peca'));
3 for (var i = pecas.length - 1; i > 0; i--) {
4 var j = Math.floor(Math.random() * (i + 1));
5 var tmp = pecas[i]; pecas[i] = pecas[j]; pecas[j] = tmp;
6 }
7 pecas.forEach(function (p) { p.classList.remove('selecionada'); pecasDiv.appendChild(p); });
8 slots.forEach(function (slot) { slot.classList.remove('preenchido'); });
9 selecionada = null;
10 painel.hidden = true;
11 verificar.hidden = false;
12 reset.hidden = true;
13 });