Зауважимо: коли користувач заповнює довгу форму — анкету, заявку або опитування — будь-яке випадкове закриття вкладки, збій мережі або перезавантаження сторінки призводить до втрати введених даних. Дослідження показують, що до 70% користувачів залишають форму після такої втрати. Це означає, що бізнес втрачає клієнтів, а користувачі витрачають час даремно. Автозбереження прогресу вирішує цю проблему: дані зберігаються в реальному часі та відновлюються при наступному візиті. Ми розробляємо гібридне рішення, яке працює і в offline, і на сервері, забезпечуючи збереження навіть при обриві з'єднання.
У цій статті розберемо технічні деталі реалізації: стратегії зберігання (localStorage, IndexedDB, сервер), синхронізацію, індикацію стану та обробку конфліктів при паралельному редагуванні. Ви побачите готовий код на JavaScript та Laravel, який можна адаптувати під свій проєкт. Рішення підходить для React, Vue, Angular та будь-яких інших фреймворків.
Середня довжина форм для лендінгів — 10–15 полів, для складних анкет — до 50 полів. Чим більша форма, тим вищий ризик втрати даних. Наприклад, багатокрокова форма реєстрації на конференцію із завантаженням документів може складатися з 5–7 кроків. Без автозбереження користувач повторює весь шлях після збою. З нашим рішенням він повертається на перерваний крок, а чернетка зберігається 30 днів на сервері.
Чому гібридний підхід кращий?
Гібридний підхід поєднує швидкість локального сховища та надійність сервера. LocalStorage забезпечує миттєве збереження без затримок мережі — час доступу менше 1 мс. Згідно з MDN Web Docs, localStorage обмежений 5-10 МБ. Серверна синхронізація дозволяє продовжити заповнення з іншого пристрою. Це найкращий вибір для форм, що вимагають високої відмовостійкості. Для неавторизованих користувачів дані зберігаються лише локально, для авторизованих — дублюються на сервері. Гібридний підхід у 5 разів знижує ризик втрати даних порівняно з лише локальним зберіганням.
Які стратегії зберігання обрати?
| Стратегія | Швидкість доступу | Офлайн | Міжплатформенність | Ліміт | Складність |
|---|---|---|---|---|---|
| LocalStorage | <1 мс | Так | Ні | 5–10 МБ | Низька |
| IndexedDB | 2–5 мс | Так | Ні | Не обмежений | Середня |
| Сервер (API) | 50–500 мс | Ні | Так | Не обмежений | Висока |
| Гібрид | <1 мс (локально) | Так | Так | 5–10 МБ + сервер | Середня |
LocalStorage підходить для форм з невеликим обсягом даних (до 10 МБ). IndexedDB — для бінарних даних (зображення, файли). Сервер — для авторизованих користувачів з доступом з будь-яких пристроїв.
Як працює автозбереження?
В основі лежить клас FormAutoSave з debounce-механізмом (800 мс). Дані серіалізуються в JSON і зберігаються в localStorage. При відновленні перевіряється термін придатності чернетки (7 днів). Приклад реалізації:
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();
}
Як уникнути втрати даних при обриві з'єднання? — розробка форми з
При обриві мережі локальне сховище продовжує працювати. Як тільки з'єднання відновлюється, дані синхронізуються з сервером. Для цього використовуємо navigator.onLine та подію online. Якщо запит на сервер провалився, дані залишаються в localStorage і повторяться при наступній спробі. Такий підхід гарантує збереження навіть при нестабільному інтернеті. За нашими даними, до 15% користувачів працюють з формами в умовах поганого зв'язку.
Індикатор стану
Користувач бачить статус збереження. Використовуємо 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>
);
}
Конфлікти при паралельному редагуванні
Якщо форма відкрита в кількох вкладках, синхронізуємо стан через подію storage:
window.addEventListener('storage', (e) => {
if (e.key === 'form_draft_application_v1') {
const remote = JSON.parse(e.newValue);
if (remote.savedAt > localSavedAt) {
applyRemoteData(remote.data);
}
}
});
Додаткові поради щодо налаштування debounce
Для форм з великою кількістю полів (більше 20) збільште debounce до 1–1.5 секунд, щоб знизити навантаження на localStorage. На мобільних пристроях використовуйте throttle замість debounce для контролю частоти збереження.
Як впровадити автозбереження форми в проєкт?
- Визначте ключ для зберігання (наприклад,
form_draft_{id}). - Ініціалізуйте
FormAutoSaveпри монтуванні форми. - Додайте індикатор стану за допомогою компонента
AutoSaveIndicator. - Для авторизованих користувачів підключіть серверну синхронізацію.
- Протестуйте відновлення після закриття вкладки та обриву мережі.
Що входить у нашу роботу?
- Аналіз поточної форми та користувацьких сценаріїв.
- Проєктування схеми зберігання (localStorage + сервер).
- Реалізація автозбереження з debounce 800 мс.
- Індикатор стану (React/Vue/Angular).
- Серверна синхронізація (Laravel, Node.js, Django).
- Обробка конфліктів при паралельному редагуванні.
- Тестування на різних пристроях та в умовах поганої мережі.
- Документація та код-рев'ю.
Строки та вартість
| Етап | Строк |
|---|---|
| Базова реалізація (localStorage + індикатор) | 2–3 дні |
| З серверною синхронізацією | 5–7 днів |
| З підтримкою wizard та конфлікт-резолюцією | 7–10 днів |
Вартість розробки варіюється від 30 000 до 150 000 рублів залежно від складності. Зв'яжіться з нами, щоб отримати оцінку вашого проєкту. Замовте розробку автозбереження для вашої форми вже сьогодні! Отримайте консультацію інженера.







