Разработка интерактивных упражнений (drag-drop, fill-in-blank) для LMS
Интерактивные упражнения удерживают внимание студента лучше, чем пассивное чтение и стандартные тесты с вариантами ответов. Drag-and-drop, fill-in-the-blank, сортировка, сопоставление — всё это повышает вовлечённость и улучшает запоминание через активное воспроизведение материала.
Типы интерактивных упражнений
Drag-and-drop — перетаскивание элементов:
- Сортировка: расположить шаги алгоритма в правильном порядке
- Сопоставление: соединить понятие с определением
- Заполнение схемы: перетащить метки на диаграмму
Fill-in-the-blank — заполнение пропусков в тексте или коде
Hotspot — кликнуть на правильную область изображения
Matching pairs — найти пары карточек (memory game)
Code arrangement — собрать работающий код из перемешанных строк
Технологии
dnd-kit — рекомендуется для React. Доступность из коробки (keyboard navigation), отличная производительность, гибкая архитектура:
import { DndContext, useDraggable, useDroppable, closestCenter } from '@dnd-kit/core';
import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
interface SortableItem {
id: string;
content: string;
}
function SortableExercise({ items, onComplete }: { items: SortableItem[]; onComplete: (order: string[]) => void }) {
const [activeItems, setActiveItems] = useState(shuffle(items));
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
if (!over || active.id === over.id) return;
setActiveItems(prev => {
const oldIndex = prev.findIndex(i => i.id === active.id);
const newIndex = prev.findIndex(i => i.id === over.id);
return arrayMove(prev, oldIndex, newIndex);
});
};
const checkAnswer = () => {
const currentOrder = activeItems.map(i => i.id);
const correctOrder = items.map(i => i.id);
const isCorrect = currentOrder.every((id, idx) => id === correctOrder[idx]);
onComplete(currentOrder);
return isCorrect;
};
return (
<DndContext onDragEnd={handleDragEnd} collisionDetection={closestCenter}>
<SortableContext items={activeItems} strategy={verticalListSortingStrategy}>
{activeItems.map(item => <SortableItemCard key={item.id} item={item} />)}
</SortableContext>
<button onClick={checkAnswer}>Проверить</button>
</DndContext>
);
}
Fill-in-the-blank
// Парсинг шаблона: "Функция {{fn}} принимает {{arg}} аргументов"
function FillInBlankExercise({ template, answers }: { template: string; answers: Record<string, string> }) {
const [userAnswers, setUserAnswers] = useState<Record<string, string>>({});
const parts = template.split(/(\{\{[^}]+\}\})/g);
return (
<div className="exercise-text">
{parts.map((part, idx) => {
const match = part.match(/^\{\{(.+)\}\}$/);
if (match) {
const fieldId = match[1];
return (
<input
key={idx}
className="blank-input"
style={{ width: `${Math.max(answers[fieldId]?.length * 10, 80)}px` }}
value={userAnswers[fieldId] || ''}
onChange={e => setUserAnswers(prev => ({ ...prev, [fieldId]: e.target.value }))}
onBlur={() => checkSingleBlank(fieldId, userAnswers[fieldId], answers[fieldId])}
/>
);
}
return <span key={idx}>{part}</span>;
})}
</div>
);
}
Структура данных упражнений
interface Exercise {
id: string;
type: 'sort' | 'fill-blank' | 'match' | 'hotspot' | 'code-arrange';
title: string;
description?: string;
content: ExerciseContent; // Тип зависит от type
hints?: string[];
maxAttempts?: number;
points: number;
}
interface SortExercise extends Exercise {
type: 'sort';
content: {
items: { id: string; text: string }[];
correctOrder: string[];
explanation?: string;
};
}
Редактор упражнений для преподавателей
Drag-and-drop конструктор, где преподаватель создаёт упражнения без кода:
- Добавление элементов через форму
- Перетаскивание для установки правильного порядка
- Превью в режиме студента
- Импорт из Excel для массового создания
Сохранение прогресса
// Автосохранение при изменениях (debounce 2 сек)
const saveProgress = debounce(async (exerciseId, answers) => {
await api.post(`/exercises/${exerciseId}/save-draft`, { answers });
}, 2000);
// При финальной отправке
async function submitExercise(exerciseId, answers) {
const result = await api.post(`/exercises/${exerciseId}/submit`, { answers });
return result; // { score, correct, feedback, explanation }
}
Сроки
Drag-and-drop сортировка с проверкой ответов — 3–4 дня. Fill-in-the-blank с парсером шаблонов — 2–3 дня. Matching pairs и hotspot — 3–4 дня. Редактор упражнений для преподавателей — 5–7 дней.







