Ligar Colunas
O aprendiz conecta itens de uma coluna com seus correspondentes em outra. Avalia associação de conceitos, definições, exemplos e categorias.
Experimente
Clique em um item da esquerda e depois no correspondente da direita:
Termos
Definições
Código-fonte
Dados de exemplo
Pares fixos — a coluna direita embaralha a ORDEM DE EXIBIÇÃO, nunca o índice que identifica cada par.
| 1 |
<?php
|
| 2 |
$instrucao = 'Clique em um item da esquerda e depois no correspondente da direita:';
|
| 3 |
$pares = [
|
| 4 |
0 => ['esquerda' => 'HTML', 'direita' => 'Estrutura da página'],
|
| 5 |
1 => ['esquerda' => 'CSS', 'direita' => 'Aparência da página'],
|
| 6 |
2 => ['esquerda' => 'JavaScript', 'direita' => 'Comportamento da página'],
|
| 7 |
];
|
| 8 |
$indicesDireita = array_keys($pares);
|
| 9 |
shuffle($indicesDireita);
|
| 10 |
?>
|
Estrutura das duas colunas
Cada botão carrega seu índice (data-index); os da esquerda também recebem uma cor própria (data-cor), que a definição herda quando o par se forma.
| 1 |
<div class="questao" data-tipo="correspondencia-colunas">
|
| 2 |
|
| 3 |
<p class="questao-instrucao"><?= htmlspecialchars($instrucao) ?></p>
|
| 4 |
|
| 5 |
<div class="colunas">
|
| 6 |
<div class="coluna">
|
| 7 |
<p class="coluna-titulo">Termos</p>
|
| 8 |
<?php foreach ($pares as $i => $par): ?>
|
| 9 |
<button type="button" class="col-esquerda" data-index="<?= $i ?>" data-cor="<?= $i % 6 ?>">
|
| 10 |
<?= htmlspecialchars($par['esquerda']) ?>
|
| 11 |
</button>
|
| 12 |
<?php endforeach; ?>
|
| 13 |
</div>
|
| 14 |
|
| 15 |
<div class="coluna">
|
| 16 |
<p class="coluna-titulo">Definições</p>
|
| 17 |
<?php foreach ($indicesDireita as $i): $par = $pares[$i]; ?>
|
| 18 |
<button type="button" class="col-direita" data-index="<?= $i ?>">
|
| 19 |
<?= htmlspecialchars($par['direita']) ?>
|
| 20 |
</button>
|
| 21 |
<?php endforeach; ?>
|
| 22 |
</div>
|
| 23 |
</div>
|
Lista de pares conectados
Caixa vazia, preenchida pelo JavaScript a cada par formado; o modelo abaixo define a linha, com um botão para desfazer antes de verificar.
| 1 |
<div class="pares-lista" hidden></div>
|
| 2 |
<template class="par-template">
|
| 3 |
<div class="par">
|
| 4 |
<span class="par-esquerda"></span>
|
| 5 |
<span class="par-seta" aria-hidden="true">→</span>
|
| 6 |
<span class="par-direita"></span>
|
| 7 |
<button type="button" class="par-remover" aria-label="Remover esta correspondência">×</button>
|
| 8 |
</div>
|
| 9 |
</template>
|
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>
|
Duas colunas
Termos à esquerda, definições à direita, lado a lado.
| 1 |
.colunas {
|
| 2 |
display: grid;
|
| 3 |
grid-template-columns: 1fr 1fr;
|
| 4 |
gap: 16px;
|
| 5 |
margin-bottom: 20px;
|
| 6 |
}
|
| 7 |
|
| 8 |
.coluna-titulo {
|
| 9 |
margin: 0 0 8px;
|
| 10 |
font-size: 11px;
|
| 11 |
font-weight: 700;
|
| 12 |
letter-spacing: 0.05em;
|
| 13 |
text-transform: uppercase;
|
| 14 |
color: #64748b;
|
| 15 |
}
|
Caixas dos termos e definições
Botão de largura total, texto alinhado à esquerda.
| 1 |
.col-esquerda,
|
| 2 |
.col-direita {
|
| 3 |
display: block;
|
| 4 |
width: 100%;
|
| 5 |
margin-bottom: 8px;
|
| 6 |
padding: 10px 12px;
|
| 7 |
border: 1px solid #cbd5e1;
|
| 8 |
border-radius: 8px;
|
| 9 |
background: #f8fafc;
|
| 10 |
font: inherit;
|
| 11 |
text-align: left;
|
| 12 |
cursor: pointer;
|
| 13 |
}
|
Termo selecionado
Realce neutro enquanto aguarda o clique na definição correspondente.
| 1 |
.selecionada {
|
| 2 |
outline: 2px solid #6366f1;
|
| 3 |
background: rgba(99, 102, 241, 0.1);
|
| 4 |
}
|
Cor por par
Cada termo tem uma cor fixa (data-cor, de 0 a 5); a definição herda a cor do termo quando o par se forma, então as duas caixas de uma dupla sempre combinam — e nunca com as de outro par.
| 1 |
.associada[data-cor="0"] { border-color: #f59e0b; background: rgba(245, 158, 11, 0.1); }
|
| 2 |
.associada[data-cor="1"] { border-color: #8b5cf6; background: rgba(139, 92, 246, 0.1); }
|
| 3 |
.associada[data-cor="2"] { border-color: #06b6d4; background: rgba(6, 182, 212, 0.1); }
|
| 4 |
.associada[data-cor="3"] { border-color: #d946ef; background: rgba(217, 70, 239, 0.1); }
|
| 5 |
.associada[data-cor="4"] { border-color: #14b8a6; background: rgba(20, 184, 166, 0.1); }
|
| 6 |
.associada[data-cor="5"] { border-color: #f97316; background: rgba(249, 115, 22, 0.1); }
|
Resultado da verificação
Verde para quem acertou o par certo, vermelho para quem ficou sem par ou casou com o errado — sobrepõe a cor do par, porque o resultado importa mais nesse momento.
| 1 |
.acerto { border-color: #22c55e; background: rgba(34, 197, 94, 0.1); }
|
| 2 |
.erro { border-color: #ef4444; background: rgba(239, 68, 68, 0.1); }
|
Lista de pares conectados
Caixa com uma linha por par formado, cada linha na mesma cor da dupla nas colunas acima.
| 1 |
.pares-lista {
|
| 2 |
margin-bottom: 20px;
|
| 3 |
padding: 16px;
|
| 4 |
border: 1px solid #cbd5e1;
|
| 5 |
border-radius: 12px;
|
| 6 |
background: #f8fafc;
|
| 7 |
}
|
| 8 |
|
| 9 |
.par {
|
| 10 |
display: flex;
|
| 11 |
align-items: center;
|
| 12 |
justify-content: space-between;
|
| 13 |
gap: 12px;
|
| 14 |
padding: 10px 12px;
|
| 15 |
margin-bottom: 8px;
|
| 16 |
border: 1px solid #cbd5e1;
|
| 17 |
border-radius: 8px;
|
| 18 |
font-size: 14px;
|
| 19 |
}
|
| 20 |
|
| 21 |
.par:last-child {
|
| 22 |
margin-bottom: 0;
|
| 23 |
}
|
| 24 |
|
| 25 |
.par[data-cor="0"] { border-color: #f59e0b; background: rgba(245, 158, 11, 0.08); }
|
| 26 |
.par[data-cor="1"] { border-color: #8b5cf6; background: rgba(139, 92, 246, 0.08); }
|
| 27 |
.par[data-cor="2"] { border-color: #06b6d4; background: rgba(6, 182, 212, 0.08); }
|
| 28 |
.par[data-cor="3"] { border-color: #d946ef; background: rgba(217, 70, 239, 0.08); }
|
| 29 |
.par[data-cor="4"] { border-color: #14b8a6; background: rgba(20, 184, 166, 0.08); }
|
| 30 |
.par[data-cor="5"] { border-color: #f97316; background: rgba(249, 115, 22, 0.08); }
|
| 31 |
|
| 32 |
.par-seta {
|
| 33 |
color: #6366f1;
|
| 34 |
font-weight: 700;
|
| 35 |
}
|
Remover um par
Botão pequeno e circular, ativo só antes de verificar.
| 1 |
.par-remover {
|
| 2 |
flex-shrink: 0;
|
| 3 |
width: 24px;
|
| 4 |
height: 24px;
|
| 5 |
border: none;
|
| 6 |
border-radius: 999px;
|
| 7 |
background: #e2e8f0;
|
| 8 |
color: #475569;
|
| 9 |
font-size: 14px;
|
| 10 |
line-height: 1;
|
| 11 |
cursor: pointer;
|
| 12 |
}
|
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 colunas.
| 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 |
}
|
Encontrar os elementos da questão
Localiza as duas colunas, a lista de pares, o modelo de linha e as ações.
| 1 |
document.addEventListener('DOMContentLoaded', function () {
|
| 2 |
document.querySelectorAll('.questao[data-tipo="correspondencia-colunas"]').forEach(function (questao) {
|
| 3 |
var esquerda = questao.querySelectorAll('.col-esquerda');
|
| 4 |
var direita = questao.querySelectorAll('.col-direita');
|
| 5 |
var lista = questao.querySelector('.pares-lista');
|
| 6 |
var template = questao.querySelector('.par-template');
|
| 7 |
var verificar = questao.querySelector('.questao-verificar');
|
| 8 |
var painel = questao.querySelector('.questao-painel');
|
| 9 |
var reset = questao.querySelector('.questao-reset');
|
| 10 |
// O item da esquerda selecionado, aguardando o clique no par da direita.
|
| 11 |
var selecionado = null;
|
| 12 |
|
| 13 |
function itemDireita(indice) {
|
| 14 |
return questao.querySelector('.col-direita[data-index="' + indice + '"]');
|
| 15 |
}
|
| 16 |
|
| 17 |
function limparSelecao() {
|
| 18 |
if (selecionado) selecionado.classList.remove('selecionada');
|
| 19 |
selecionado = null;
|
| 20 |
}
|
Lista de pares conectados
Cria ou atualiza a linha do par (na mesma cor da esquerda) e permite desfazer com o × — só antes de verificar.
| 1 |
function entradaPar(itemEsq) {
|
| 2 |
return lista.querySelector('.par[data-index="' + itemEsq.dataset.index + '"]');
|
| 3 |
}
|
| 4 |
|
| 5 |
function criarOuAtualizarPar(itemEsq, itemDir) {
|
| 6 |
var entrada = entradaPar(itemEsq);
|
| 7 |
if (!entrada) {
|
| 8 |
var fragmento = template.content.cloneNode(true);
|
| 9 |
entrada = fragmento.querySelector('.par');
|
| 10 |
entrada.dataset.index = itemEsq.dataset.index;
|
| 11 |
lista.appendChild(fragmento);
|
| 12 |
}
|
| 13 |
entrada.dataset.cor = itemEsq.dataset.cor;
|
| 14 |
entrada.querySelector('.par-esquerda').textContent = itemEsq.textContent.trim();
|
| 15 |
entrada.querySelector('.par-direita').textContent = itemDir.textContent.trim();
|
| 16 |
lista.hidden = false;
|
| 17 |
}
|
| 18 |
|
| 19 |
function removerEntradaPar(itemEsq) {
|
| 20 |
var entrada = entradaPar(itemEsq);
|
| 21 |
if (entrada) entrada.remove();
|
| 22 |
lista.hidden = !lista.children.length;
|
| 23 |
}
|
| 24 |
|
| 25 |
lista.addEventListener('click', function (evento) {
|
| 26 |
var botao = evento.target.closest('.par-remover');
|
| 27 |
if (!botao || verificar.disabled) return;
|
| 28 |
var entrada = botao.closest('.par');
|
| 29 |
var itemEsq = questao.querySelector('.col-esquerda[data-index="' + entrada.dataset.index + '"]');
|
| 30 |
var itemDir = itemEsq.dataset.associado ? itemDireita(itemEsq.dataset.associado) : null;
|
| 31 |
delete itemEsq.dataset.associado;
|
| 32 |
itemEsq.classList.remove('associada');
|
| 33 |
if (itemDir) { itemDir.classList.remove('associada'); delete itemDir.dataset.cor; }
|
| 34 |
removerEntradaPar(itemEsq);
|
| 35 |
});
|
Selecionar um termo
Clique alterna a seleção; clicar em outro termo troca, sem mexer no par que ele já tinha formado.
| 1 |
esquerda.forEach(function (item) {
|
| 2 |
item.addEventListener('click', function () {
|
| 3 |
if (verificar.disabled) return;
|
| 4 |
if (selecionado === item) { limparSelecao(); return; }
|
| 5 |
limparSelecao();
|
| 6 |
selecionado = item;
|
| 7 |
item.classList.add('selecionada');
|
| 8 |
});
|
| 9 |
});
|
Associar à definição
Só reage com um termo selecionado; a definição herda a cor do termo, e a lista de pares ganha (ou atualiza) uma linha.
| 1 |
direita.forEach(function (item) {
|
| 2 |
item.addEventListener('click', function () {
|
| 3 |
if (!selecionado || verificar.disabled) return;
|
| 4 |
|
| 5 |
var anterior = Array.prototype.find.call(esquerda, function (e) {
|
| 6 |
return e.dataset.associado === item.dataset.index;
|
| 7 |
});
|
| 8 |
var antigaDireita = selecionado.dataset.associado
|
| 9 |
? itemDireita(selecionado.dataset.associado)
|
| 10 |
: null;
|
| 11 |
|
| 12 |
if (anterior && anterior !== selecionado) {
|
| 13 |
delete anterior.dataset.associado;
|
| 14 |
anterior.classList.remove('associada');
|
| 15 |
removerEntradaPar(anterior);
|
| 16 |
}
|
| 17 |
if (antigaDireita && antigaDireita !== item) {
|
| 18 |
antigaDireita.classList.remove('associada');
|
| 19 |
delete antigaDireita.dataset.cor;
|
| 20 |
}
|
| 21 |
|
| 22 |
selecionado.dataset.associado = item.dataset.index;
|
| 23 |
selecionado.classList.remove('selecionada');
|
| 24 |
selecionado.classList.add('associada');
|
| 25 |
item.classList.add('associada');
|
| 26 |
item.dataset.cor = selecionado.dataset.cor;
|
| 27 |
criarOuAtualizarPar(selecionado, item);
|
| 28 |
selecionado = null;
|
| 29 |
});
|
| 30 |
});
|
Verificar
Um par está certo quando o índice associado bate com o próprio índice do termo — o mesmo índice nos dois lados é o gabarito.
| 1 |
verificar.addEventListener('click', function () {
|
| 2 |
var acertos = 0;
|
| 3 |
esquerda.forEach(function (item) {
|
| 4 |
var correto = item.dataset.associado === item.dataset.index;
|
| 5 |
item.classList.remove('associada');
|
| 6 |
item.classList.add(correto ? 'acerto' : 'erro');
|
| 7 |
if (correto) acertos++;
|
| 8 |
});
|
| 9 |
|
| 10 |
painel.querySelector('.questao-painel-mensagem').textContent = acertos === esquerda.length
|
| 11 |
? 'Correto! Todos os pares correspondidos.'
|
| 12 |
: acertos + ' de ' + esquerda.length + ' pares corretos.';
|
| 13 |
painel.hidden = false;
|
| 14 |
|
| 15 |
// Os pares viraram resultado: o × deixa de fazer sentido depois de
|
| 16 |
// verificado.
|
| 17 |
lista.querySelectorAll('.par-remover').forEach(function (b) { b.hidden = true; });
|
| 18 |
|
| 19 |
verificar.hidden = true;
|
| 20 |
reset.hidden = false;
|
| 21 |
});
|
Reiniciar
Desfaz todas as associações, esvazia a lista de pares e devolve o estado inicial.
| 1 |
reset.addEventListener('click', function () {
|
| 2 |
selecionado = null;
|
| 3 |
esquerda.forEach(function (item) {
|
| 4 |
delete item.dataset.associado;
|
| 5 |
item.classList.remove('selecionada', 'associada', 'acerto', 'erro');
|
| 6 |
});
|
| 7 |
direita.forEach(function (item) {
|
| 8 |
item.classList.remove('associada');
|
| 9 |
delete item.dataset.cor;
|
| 10 |
});
|
| 11 |
lista.textContent = '';
|
| 12 |
lista.hidden = true;
|
| 13 |
painel.hidden = true;
|
| 14 |
verificar.hidden = false;
|
| 15 |
reset.hidden = true;
|
| 16 |
});
|