פיתוח טופס רב-שלבי עבור 1C-Bitrix
טופס ארוך בעמוד אחד הורג המרות — כך אישרו בדיקות A/B שלנו על פני 40+ פרויקטים. משתמשים רואים 10+ שדות וסוגרים את הטאב תוך 5 שניות. הפתרון הוא לפצל את הטופס לשלבים של 2–4 שדות כל אחד. כל מסך מרגיש פשוט, ההתקדמות נראית לעין, וההמרה קופצת ב-30–50%. אנו מפתחים רכיבים מותאמים אישית עבור Bitrix ו-Bitrix24 עם ניווט בצד הלקוח, אימות לכל שלב, ושליחה סופית ל-CRM. הבסיס הוא מנגנון הרכיב 2.0 הסטנדרטי ו-infoblocks v2.0. הצד השרת יוצר לידים דרך CCrmLead, תומך בתהליכים עסקיים וב-REST API. עבור טפסים ארוכים, אנו מיישמים שמירת התקדמות ב-localStorage כדי למנוע אובדן נתונים בסגירה מקרית. גישה זו הניבה צמיחה של פי 2–3 בהמרות על פני 20 פרויקטים בממוצע.
| מאפיין | טופס בעמוד אחד | טופס רב-שלבי |
|---|---|---|
| המרה | 2–5% | 8–15% |
| שיעור נטישה | 30–50% | 10–20% |
| זמן מילוי | 3–5 דקות | 1–2 דקות (סובייקטיבי) |
| שימושיות | נמוכה | גבוהה |
מדוע טופס רב-שלבי מעלה המרות?
סרגל ההתקדמות ופיצול השלבים מורידים פסיכולוגית את סף הכניסה. משתמשים רואים "רק 3 שלבים" ומוכנים להתחיל. כל שלב שהושלם נותן תחושת התקדמות. בפועל, אנו רואים עלייה של 40% בשיעורי השלמה לעומת טפסים בעמוד אחד. בנוסף, אימות לכל שלב מבטל שגיאות בסוף הטופס. טפסים רב-שלביים גם מניבים לידים באיכות גבוהה יותר כי המשתמשים לא מתעייפים ומזינים נתונים בקפידה רבה יותר. זה חשוב במיוחד עבור פניות B2B עם שדות טכניים.
אילו בעיות טכניות מתעוררות במהלך היישום?
האתגרים העיקריים הם סנכרון מצב בין שלבים, טיפול בשגיאות בצד השרת לאחר שליחה סופית, ואימות נכון עם מסכות קלט. אם משתמש מזין טלפון עם מסכה ואימות השרת מחמיר יותר, הליד עלול שלא להיווצר. אנו פותרים זאת על ידי איחוד אימות בין לקוח לשרת. בעיה נוספת היא אובדן נתונים בסגירת דפדפן; אנו מיישמים שמירת localStorage עם פסק זמן של 30 דקות. המומחים המוסמכים שלנו פתרו בעיות אלה על פני 50+ פרויקטים.
כיצד בנוי רכיב הטופס הרב-שלבי?
הטופס מחזיק את כל המצב בצד הלקוח כאובייקט JS ושולח נתונים בבקשה אחת לאחר השלב האחרון. השרת מקבל את מערך הנתונים המלא.
הגדרת השלבים נעשית דרך פרמטרי הרכיב (מערך PHP) או דרך ממשק הניהול באמצעות infoblocks.
// /local/components/local/multistep.form/class.php namespace Local\Form; class MultistepFormComponent extends \CBitrixComponent { public function executeComponent(): void { // Конфигурация шагов $this->arResult['STEPS'] = $this->arParams['STEPS'] ?? $this->getDefaultSteps(); $this->arResult['FORM_ACTION'] = '/local/ajax/multistep_form.php'; $this->arResult['SESSID'] = bitrix_sessid(); $this->includeComponentTemplate(); } private function getDefaultSteps(): array { return [ [ 'id' => 'step_personal', 'title' => 'Ваши данные', 'fields' => [ ['name' => 'name', 'label' => 'Имя', 'type' => 'text', 'required' => true], ['name' => 'phone', 'label' => 'Телефон', 'type' => 'tel', 'required' => true], ['name' => 'email', 'label' => 'Email', 'type' => 'email', 'required' => false], ], ], [ 'id' => 'step_details', 'title' => 'Детали заявки', 'fields' => [ ['name' => 'service', 'label' => 'Услуга', 'type' => 'select', 'options' => ['Консультация', 'Аудит', 'Разработка']], ['name' => 'budget', 'label' => 'Бюджет', 'type' => 'radio', 'options' => ['до 100к', '100–500к', 'от 500к']], ], ], [ 'id' => 'step_message', 'title' => 'Комментарий', 'fields' => [ ['name' => 'message', 'label' => 'Опишите задачу', 'type' => 'textarea', 'required' => false], ], ], ]; } } לוגיקת צד לקוח
class MultistepForm { constructor(formEl) { this.form = formEl; this.steps = Array.from(formEl.querySelectorAll('.form-step')); this.current = 0; this.formData = {}; this.bindEvents(); this.showStep(0); } bindEvents() { this.form.querySelectorAll('.btn-next').forEach(btn => btn.addEventListener('click', () => this.tryNext()) ); this.form.querySelectorAll('.btn-prev').forEach(btn => btn.addEventListener('click', () => this.prev()) ); this.form.addEventListener('submit', e => { e.preventDefault(); this.submit(); }); } tryNext() { if (!this.validateCurrentStep()) return; this.collectCurrentStepData(); if (this.current < this.steps.length - 1) { this.showStep(this.current + 1); } } prev() { if (this.current > 0) { this.showStep(this.current - 1); } } showStep(index) { this.steps.forEach((step, i) => { step.classList.toggle('active', i === index); }); this.current = index; this.updateProgress(); } validateCurrentStep(): boolean { const stepEl = this.steps[this.current]; let valid = true; stepEl.querySelectorAll('[required]').forEach(field => { const error = stepEl.querySelector(`[data-error-for="${field.name}"]`); if (!field.value.trim()) { valid = false; field.classList.add('error'); if (error) error.textContent = 'Это поле обязательно'; } else { field.classList.remove('error'); if (error) error.textContent = ''; } }); // Валидация телефона const phoneField = stepEl.querySelector('[name="phone"]'); if (phoneField && phoneField.value) { const phoneRegex = /^(\+7|7|8)?[\s-]?\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{2}[\s-]?\d{2}$/; if (!phoneRegex.test(phoneField.value.replace(/\s/g, ''))) { valid = false; phoneField.classList.add('error'); } } return valid; } collectCurrentStepData() { const stepEl = this.steps[this.current]; stepEl.querySelectorAll('input, select, textarea').forEach(field => { if (field.type === 'radio' || field.type === 'checkbox') { if (field.checked) this.formData[field.name] = field.value; } else { this.formData[field.name] = field.value; } }); } updateProgress() { const percent = ((this.current) / (this.steps.length - 1)) * 100; const bar = this.form.querySelector('.progress-bar-fill'); if (bar) bar.style.width = Math.min(100, percent) + '%'; const label = this.form.querySelector('.progress-label'); if (label) label.textContent = `Шаг ${this.current + 1} из ${this.steps.length}`; } async submit() { this.collectCurrentStepData(); const submitBtn = this.form.querySelector('[type="submit"]'); submitBtn.disabled = true; submitBtn.textContent = 'Отправляем...'; try { const response = await fetch(this.form.dataset.action, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ ...this.formData, sessid: this.form.dataset.sessid, }), }); const result = await response.json(); if (result.success) { this.showSuccessScreen(); } else { this.showError(result.error || 'Ошибка отправки'); submitBtn.disabled = false; submitBtn.textContent = 'Отправить'; } } catch (e) { this.showError('Ошибка соединения. Попробуйте позже.'); submitBtn.disabled = false; } } showSuccessScreen() { this.form.innerHTML = ` <div class="form-success"> <h3>Спасибо! Мы свяжемся с вами в течение часа.</h3> </div> `; } showError(message) { let errEl = this.form.querySelector('.form-global-error'); if (!errEl) { errEl = document.createElement('div'); errEl.className = 'form-global-error'; this.steps[this.current].prepend(errEl); } errEl.textContent = message; } } document.querySelectorAll('.multistep-form').forEach(el => new MultistepForm(el)); מטפל בצד השרת
// /local/ajax/multistep_form.php \Bitrix\Main\Loader::includeModule('crm'); $data = json_decode(file_get_contents('php://input'), true); $sessid = $data['sessid'] ?? ''; if (!check_bitrix_sessid($sessid)) { http_response_code(403); echo json_encode(['error' => 'Forbidden']); exit; } $name = htmlspecialchars(trim($data['name'] ?? '')); $phone = htmlspecialchars(trim($data['phone'] ?? '')); $email = htmlspecialchars(trim($data['email'] ?? '')); $service = htmlspecialchars($data['service'] ?? ''); $budget = htmlspecialchars($data['budget'] ?? ''); $message = htmlspecialchars($data['message'] ?? ''); if (empty($name) || empty($phone)) { echo json_encode(['error' => 'Не заполнены обязательные поля']); exit; } $comments = implode("\n", array_filter([ $service ? "Услуга: {$service}" : '', $budget ? "Бюджет: {$budget}" : '', $message ? "Комментарий: {$message}" : '', ])); $lead = new \CCrmLead(false); $leadId = $lead->Add([ 'TITLE' => 'Заявка с сайта — ' . $name, 'NAME' => $name, 'PHONE' => [['VALUE' => $phone, 'VALUE_TYPE' => 'WORK']], 'EMAIL' => [['VALUE' => $email, 'VALUE_TYPE' => 'WORK']], 'SOURCE_ID' => 'WEB', 'COMMENTS' => $comments, ]); echo json_encode(['success' => (bool)$leadId]); שמירת התקדמות: כיצד למנוע אובדן נתונים?
עבור טפסים ארוכים (5+ שלבים), אנו מוסיפים שמירת localStorage — בסגירה מקרית, הנתונים משוחזרים באותו שלב. פסק הזמן הוא 30 דקות; מעבר לכך, הנתונים נחשבים מיושנים.
// После каждого шага localStorage.setItem('form_progress', JSON.stringify({ step: this.current, data: this.formData, savedAt: Date.now(), })); // При инициализации — восстановить, если не старше 30 минут const saved = JSON.parse(localStorage.getItem('form_progress') || 'null'); if (saved && Date.now() - saved.savedAt < 1800000) { this.formData = saved.data; this.showStep(saved.step); } מדריך הגדרה שלב אחר שלב
- צור רכיב חדש ב-
localStorage. - הגדר את מערך השלבים עם שדות (כמו בדוגמה למעלה).
- יישם JavaScript בצד הלקוח עם אימות.
- כתוב את המטפל בשרת ליצירת לידים.
- כלול את הרכיב בעמוד והגדר פרמטרים.
טעויות יישום אופייניות
- חוסר הגנת CSRF — השתמש תמיד ב-
// /local/components/local/multistep.form/class.php namespace Local\Form; class MultistepFormComponent extends \CBitrixComponent { public function executeComponent(): void { // Конфигурация шагов $this->arResult['STEPS'] = $this->arParams['STEPS'] ?? $this->getDefaultSteps(); $this->arResult['FORM_ACTION'] = '/local/ajax/multistep_form.php'; $this->arResult['SESSID'] = bitrix_sessid(); $this->includeComponentTemplate(); } private function getDefaultSteps(): array { return [ [ 'id' => 'step_personal', 'title' => 'Ваши данные', 'fields' => [ ['name' => 'name', 'label' => 'Имя', 'type' => 'text', 'required' => true], ['name' => 'phone', 'label' => 'Телефон', 'type' => 'tel', 'required' => true], ['name' => 'email', 'label' => 'Email', 'type' => 'email', 'required' => false], ], ], [ 'id' => 'step_details', 'title' => 'Детали заявки', 'fields' => [ ['name' => 'service', 'label' => 'Услуга', 'type' => 'select', 'options' => ['Консультация', 'Аудит', 'Разработка']], ['name' => 'budget', 'label' => 'Бюджет', 'type' => 'radio', 'options' => ['до 100к', '100–500к', 'от 500к']], ], ], [ 'id' => 'step_message', 'title' => 'Комментарий', 'fields' => [ ['name' => 'message', 'label' => 'Опишите задачу', 'type' => 'textarea', 'required' => false], ], ], ]; } }. - חוסר התאמה בסוגי נתונים בין לקוח לשרת.
- התעלמות מגרסת מובייל — בדוק את ההיענות של השלבים.
שלבי פיתוח
- ניתוח — זיהוי שדות נדרשים ולוגיקת שלבים.
- עיצוב — הכנת מוקאפים והגדרת רכיב.
- פיתוח — יצירת רכיב PHP וקובץ JS.
- בדיקות — בדיקות A/B, בדיקות אימות, טיפול בשגיאות.
- פריסה — פריסה לאתר חי ואינטגרציה עם CRM.
מה כלול בעבודה
- תיעוד API עבור הרכיב והוראות התקנה.
- גישה למאגר קוד המקור.
- הדרכה למנהלים על שימוש בטופס (מילוי, עריכת שלבים).
- תמיכה באחריות למשך חודשיים לאחר הפריסה.
לוחות זמנים ועלות
| אפשרות | היקף | לוח זמנים משוער |
|---|---|---|
| טופס רב-שלבי בסיסי | 2–3 שלבים, אימות, ליד ל-CRM | 2–4 ימים |
| עם שדות מותאמים אישית | בחירה, רדיו, קובץ, שדות דינמיים | 4–7 ימים |
| רכיב מלא | שלבים הניתנים להגדרה, בדיקות A/B, אנליטיקה, שמירה | 8–12 ימים |
העלות מחושבת באופן אישי לפי מספר השלבים, מורכבות האימות וצרכי האינטגרציה. צור קשר להערכה מוקדמת עבור הפרויקט שלך. הזמן פיתוח טופס רב-שלבי כדי להעלות את ההמרה באתר שלך. קבל ייעוץ — ננתח את הטופס הנוכחי שלך ונציע את הפתרון האופטימלי.







