← Назад к вопросам
Как связана по логике работа click с onblur?
1.7 Middle🔥 201 комментариев
#Soft Skills и рабочие процессы
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Взаимосвязь Click и onBlur в браузере
Это отличный вопрос, который показывает понимание порядка срабатывания событий (event order) в браузере. Объясню связь и приведу практические примеры.
1. Порядок срабатывания событий
Сценарий: User кликает на кнопку, пока input в фокусе
// Timeline событий:
const input = document.querySelector('input');
const button = document.querySelector('button');
input.addEventListener('focus', () => console.log('1. INPUT FOCUSED'));
input.addEventListener('blur', () => console.log('3. INPUT BLURRED'));
button.addEventListener('click', () => console.log('2. BUTTON CLICKED'));
// Если user кликает на button когда input в фокусе:
// Вывод:
// 1. INPUT FOCUSED (когда user кликает на input)
// 3. INPUT BLURRED (когда user кликает на button)
// 2. BUTTON CLICKED (во время клика на button)
// ВАЖНО: blur срабатывает ДО click на другом элементе!
2. Точный порядок событий
<input type="text" id="username" placeholder="Enter username" />
<button id="submit">Submit</button>
<script>
const input = document.getElementById('username');
const button = document.getElementById('submit');
input.addEventListener('focus', (e) => {
console.log('INPUT: focus event');
});
input.addEventListener('blur', (e) => {
console.log('INPUT: blur event - element is losing focus');
});
button.addEventListener('mousedown', (e) => {
console.log('BUTTON: mousedown event');
});
button.addEventListener('click', (e) => {
console.log('BUTTON: click event');
});
button.addEventListener('mouseup', (e) => {
console.log('BUTTON: mouseup event');
});
// Если input в фокусе и user кликает на button:
// Порядок:
// 1. BUTTON: mousedown event
// 2. INPUT: blur event - element is losing focus
// 3. BUTTON: mouseup event
// 4. BUTTON: click event
</script>
3. Практический пример: Сохранение данных при blur
// Классический паттерн: сохранять при blur, но НЕ сохранять при клике на кнопку отмены
function UserProfileForm() {
const [formData, setFormData] = useState({ name: '', email: '' });
const [isSaving, setIsSaving] = useState(false);
const inputRef = useRef(null);
const handleInputBlur = async (e) => {
// blur срабатывает когда user уходит с input
// Но если он кликает на "Отмена" - мы НЕ хотим сохранять
// Решение 1: Задержка (race condition!)
setTimeout(() => {
// К этому моменту уже сработал click "Отмены"
setIsSaving(true);
api.saveUser(formData).finally(() => setIsSaving(false));
}, 100);
};
const handleCancel = () => {
// Если это сработает ДО blur - отмена сработает
// Но blur все равно может сработать из-за race condition
setFormData({ name: '', email: '' });
inputRef.current?.focus(); // Возвращаем фокус
};
return (
<div>
<input
ref={inputRef}
onBlur={handleInputBlur}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
/>
<button onClick={handleCancel}>Cancel</button>
</div>
);
}
4. Правильное решение с mouseDown
// Решение: используем mousedown вместо click для кнопки отмены
function UserProfileForm() {
const [formData, setFormData] = useState({ name: '', email: '' });
const [isSaving, setIsSaving] = useState(false);
const inputRef = useRef(null);
const isCancellingRef = useRef(false);
const handleInputBlur = async () => {
// Проверяем был ли клик на кнопку отмены
if (isCancellingRef.current) {
isCancellingRef.current = false;
return; // Не сохраняем
}
// Сохраняем при blur
setIsSaving(true);
try {
await api.saveUser(formData);
} finally {
setIsSaving(false);
}
};
// mousedown срабатывает ДО blur!
const handleCancelMouseDown = () => {
isCancellingRef.current = true;
setFormData({ name: '', email: '' });
};
return (
<div className="form">
<input
ref={inputRef}
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
onBlur={handleInputBlur}
placeholder="Username"
/>
<button
onMouseDown={handleCancelMouseDown}
onClick={() => inputRef.current?.focus()}
>
Cancel
</button>
<button type="submit">Save</button>
</div>
);
}
5. Использование onMouseDown для dropdown меню
// Классический паттерн: dropdown закрывается при blur input
// Но нужно чтобы можно было кликать на items в dropdown
function SearchWithDropdown() {
const [query, setQuery] = useState('');
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [selectedItem, setSelectedItem] = useState(null);
const handleInputBlur = () => {
// Закрываем dropdown когда input теряет фокус
// Но если user клик на item в dropdown - это не сработает!
// Потому что mousedown на item сработает ДО blur
setTimeout(() => setIsDropdownOpen(false), 100);
};
const handleItemMouseDown = (item) => {
// mousedown срабатывает ДО blur input
// Поэтому мы можем выбрать item до того как blur закроет dropdown
setSelectedItem(item);
setQuery(item.label);
setIsDropdownOpen(false);
};
return (
<div className="relative">
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
onFocus={() => setIsDropdownOpen(true)}
onBlur={handleInputBlur}
placeholder="Search..."
/>
{isDropdownOpen && (
<div className="absolute bg-white border rounded mt-1">
{items.map((item) => (
<div
key={item.id}
onMouseDown={() => handleItemMouseDown(item)}
className="p-2 hover:bg-gray-100 cursor-pointer"
>
{item.label}
</div>
))}
</div>
)}
{selectedItem && <p>Selected: {selectedItem.label}</p>}
</div>
);
}
6. Event bubbling и capturing
// blur НЕ bubbles (не всплывает), поэтому parent div не получит blur
const parent = document.getElementById('parent');
const input = document.getElementById('input');
const button = document.getElementById('button');
input.addEventListener('blur', () => {
console.log('blur на input');
});
parent.addEventListener('blur', () => {
console.log('blur на parent - НЕ СРАБОТАЕТ');
}, true); // даже с capturing=true это не сработает
// click БЫ bubbles:
button.addEventListener('click', () => {
console.log('click на button');
});
parent.addEventListener('click', () => {
console.log('click на parent - СРАБОТАЕТ (bubbling)');
});
7. focusout vs blur
// focusout это более полезный event
const input = document.getElementById('input');
input.addEventListener('blur', () => {
console.log('blur - не bubbles');
});
input.addEventListener('focusout', () => {
console.log('focusout - bubbles!');
});
const parent = document.getElementById('parent');
parent.addEventListener('focusout', () => {
console.log('focusout поймана на parent - сработает!');
});
// focusout полезнее для form validation потому что можно поймать на форме
8. Полная демонстрация порядка
// HTML
// <input id="input" />
// <button id="button">Click me</button>
const input = document.getElementById('input');
const button = document.getElementById('button');
const log = (msg) => {
const log = document.getElementById('log');
log.innerHTML += msg + '<br>';
};
// Input события
input.addEventListener('focus', () => log('INPUT: focus'));
input.addEventListener('mousedown', () => log('INPUT: mousedown'));
input.addEventListener('mouseup', () => log('INPUT: mouseup'));
input.addEventListener('click', () => log('INPUT: click'));
input.addEventListener('blur', () => log('INPUT: blur'));
// Button события
button.addEventListener('mousedown', () => log('BUTTON: mousedown'));
button.addEventListener('mouseup', () => log('BUTTON: mouseup'));
button.addEventListener('click', () => log('BUTTON: click'));
// Сценарий: input в фокусе, user кликает на button
// Вывод:
// BUTTON: mousedown
// INPUT: blur
// BUTTON: mouseup
// BUTTON: click
9. Практическая применение в React
interface EditableFieldProps {
initialValue: string;
onSave: (value: string) => Promise<void>;
}
function EditableField({ initialValue, onSave }: EditableFieldProps) {
const [value, setValue] = useState(initialValue);
const [isEditing, setIsEditing] = useState(false);
const isSavingRef = useRef(false);
const handleBlur = async () => {
if (value === initialValue || isSavingRef.current) return;
try {
isSavingRef.current = true;
await onSave(value);
} catch (error) {
setValue(initialValue);
alert('Failed to save');
} finally {
isSavingRef.current = false;
setIsEditing(false);
}
};
const handleCancelMouseDown = () => {
// mousedown срабатывает ДО blur
setValue(initialValue);
};
if (!isEditing) {
return (
<div
onClick={() => setIsEditing(true)}
className="cursor-pointer p-2 hover:bg-gray-100"
>
{value}
</div>
);
}
return (
<div className="flex gap-2">
<input
autoFocus
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={handleBlur}
/>
<button onMouseDown={handleCancelMouseDown} onClick={() => setIsEditing(false)}>
Cancel
</button>
</div>
);
}
10. Key Takeaway
// Критически важный факт о порядке событий:
// Когда user кликает на элемент который не в фокусе:
// 1. Element теряющий фокус: BLUR срабатывает ПЕРВЫМ
// 2. Element получающий фокус: FOCUS срабатывает
// 3. Element в котором был клик: MOUSEDOWN -> MOUSEUP -> CLICK
// Используйте это для:
// - Сохранения при blur с проверкой mousedown флага
// - Dropdown меню (mousedown вместо click)
// - Отмены операций с mousedown
// - Form validation на focusout (не blur)
Итого
- blur срабатывает раньше click - это ключевой момент
- blur не bubbles - используйте focusout если нужен bubbling
- Используйте mousedown для кнопок в dropdowns и отмены
- focusout более полезнее для form-level обработки
- Порядок: mousedown -> blur -> mouseup -> click
Это знание критично для правильной работы с формами, dropdown меню и сохранением данных при потере фокуса.