Розробка форми з автозбереженням прогресу на сайті
Довгі форми — анкети, заявки, опитування — втрачають користувачів, якщо ті випадково закрили вкладку або з'єднання перервалося. Автозбереження вирішує цю проблему: дані переживають перезавантаження сторінки та повертаються при наступному візиті.
Стратегії зберігання
LocalStorage — швидко, без сервера, але привязано до пристрою та браузера. Підходить для анонімних форм або чорновиків.
IndexedDB — те ж саме, але для великих даних (файли, бінарники). Використовуємо через idb або localforage.
Сервер (API) — дані доступні з будь-якого пристрою. Потребує авторизації або часового токена сесії.
Гібрид — спочатку в localStorage (миттєво), потім синхронізація з сервером у фоні.
Реалізація через localStorage
class FormAutoSave {
constructor(formId, key, debounceMs = 800) {
this.form = document.getElementById(formId);
this.key = key;
this.debounceMs = debounceMs;
this._timer = null;
this._init();
}
_init() {
this._restore();
this.form.addEventListener('input', () => this._schedulesSave());
this.form.addEventListener('change', () => this._schedulesSave());
this.form.addEventListener('submit', () => this.clear());
}
_schedulesSave() {
clearTimeout(this._timer);
this._timer = setTimeout(() => this._save(), this.debounceMs);
}
_save() {
const data = {};
const elements = this.form.elements;
for (const el of elements) {
if (!el.name) continue;
if (el.type === 'checkbox') {
data[el.name] = el.checked;
} else if (el.type === 'radio') {
if (el.checked) data[el.name] = el.value;
} else {
data[el.name] = el.value;
}
}
localStorage.setItem(this.key, JSON.stringify({
data,
savedAt: Date.now(),
}));
this._updateIndicator('saved');
}
_restore() {
const raw = localStorage.getItem(this.key);
if (!raw) return;
const { data, savedAt } = JSON.parse(raw);
const age = Date.now() - savedAt;
if (age > 7 * 24 * 60 * 60 * 1000) { // 7 днів — застаріло
this.clear();
return;
}
for (const [name, value] of Object.entries(data)) {
const el = this.form.elements[name];
if (!el) continue;
if (el.type === 'checkbox') el.checked = value;
else if (el.type === 'radio') {
const radio = this.form.querySelector(`[name="${name}"][value="${value}"]`);
if (radio) radio.checked = true;
} else {
el.value = value;
}
}
this._updateIndicator('restored');
}
clear() {
localStorage.removeItem(this.key);
}
_updateIndicator(state) {
const el = document.querySelector('[data-autosave-status]');
if (!el) return;
el.textContent = state === 'saved' ? 'Збережено' : 'Чорновик відновлено';
el.dataset.state = state;
}
}
// Використання
new FormAutoSave('application-form', 'form_draft_application_v1');
Синхронізація з сервером
Коли користувач авторизований, чорновик зберігається на сервері:
async function syncDraftToServer(formKey, data) {
try {
await fetch('/api/drafts', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': window.csrfToken,
},
body: JSON.stringify({ key: formKey, data }),
});
} catch {
// Fallback — залишаємо в localStorage, спробуємо пізніше
}
}
На Laravel:
// routes/api.php
Route::middleware('auth')->group(function () {
Route::put('/drafts', [DraftController::class, 'store']);
Route::get('/drafts/{key}', [DraftController::class, 'show']);
Route::delete('/drafts/{key}', [DraftController::class, 'destroy']);
});
// DraftController
public function store(Request $request)
{
Draft::updateOrCreate(
['user_id' => auth()->id(), 'key' => $request->key],
['data' => $request->data, 'expires_at' => now()->addDays(30)]
);
return response()->noContent();
}
Індикатор стану
Користувач повинен бачити, коли форма збережена. Три стани: "Зберігається...", "Збережено 5 хвилин тому", "Помилка збереження".
// React-варіант
function AutoSaveIndicator({ status, savedAt }) {
const labels = {
saving: 'Зберігається...',
saved: savedAt ? `Збережено ${formatRelative(savedAt)}` : 'Збережено',
error: 'Помилка збереження',
idle: '',
};
return (
<span className={`autosave-badge autosave-badge--${status}`}>
{labels[status]}
</span>
);
}
Мультишагові форми (wizard)
Для wizard-форм зберігаємо не тільки дані полів, але й поточний крок:
const state = {
step: currentStep,
fields: collectFields(),
completedSteps: [1, 2, 3],
};
localStorage.setItem('wizard_draft', JSON.stringify(state));
При відновленні питаємо користувача: "Продовжити з кроку 3 або почати заново?" — модальне вікно з двома кнопками.
Конфлікти при паралельному редагуванні
Якщо форма відкрита в кількох вкладках, використовуємо localStorage event для синхронізації:
window.addEventListener('storage', (e) => {
if (e.key === 'form_draft_application_v1') {
const remote = JSON.parse(e.newValue);
if (remote.savedAt > localSavedAt) {
applyRemoteData(remote.data);
}
}
});
Терміни
Автозбереження в localStorage з відновленням та індикатором — 2–3 робочих дні. З серверною синхронізацією, підтримкою wizard, конфлікт-резолюцією та історією чорновиків — 5–7 днів.







