Files

152 lines
4.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: motion/magnetic-button
title: Magnetic Button Effect
description: Buttons that follow cursor within radius. Creates premium tactile feel.
tags: [motion, magnetic, button, micro-interaction, premium]
source: raycast.com, linear.app, brad.co, amppz.com
---
# Magnetic Button Effect
## Что это
Кнопки и элементы, которые притягиваются к курсору мыши в пределах радиуса. Создаёт ощущение тактильности и премиальности.
## Параметры
| Параметр | Значение |
|----------|----------|
| **Radius** | 60–100px |
| **Strength** | 0.1–0.3 |
| **Easing** | `cubic-bezier(0.25, 0.1, 0.25, 1)` |
| **Duration** | 0.3s |
## Реализация
```html
<button class="magnetic-btn">Hover me</button>
```
```css
.magnetic-btn {
padding: 16px 32px;
background: var(--color-accent);
color: white;
border: none;
border-radius: 12px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
position: relative;
transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
}
```
```javascript
class MagneticButton {
constructor(element, strength = 0.2, radius = 80) {
this.el = element;
this.strength = strength;
this.radius = radius;
this.rect = element.getBoundingClientRect();
this.isHovering = false;
this.animationId = null;
this.pos = { x: 0, y: 0 };
this.target = { x: 0, y: 0 };
this.init();
}
init() {
this.el.addEventListener('mouseenter', (e) => {
this.isHovering = true;
this.rect = this.el.getBoundingClientRect();
});
this.el.addEventListener('mouseleave', () => {
this.isHovering = false;
this.target = { x: 0, y: 0 };
this.animate();
});
this.el.addEventListener('mousemove', (e) => {
const x = e.clientX - this.rect.left - this.rect.width / 2;
const y = e.clientY - this.rect.top - this.rect.height / 2;
this.target = { x: x * this.strength, y: y * this.strength };
});
this.animate();
}
animate() {
if (this.isHovering) {
this.pos.x += (this.target.x - this.pos.x) * 0.1;
this.pos.y += (this.target.y - this.pos.y) * 0.1;
} else {
this.pos.x *= 0.9;
this.pos.y *= 0.9;
}
this.el.style.transform = `translate(${this.pos.x}px, ${this.pos.y}px)`;
this.animationId = requestAnimationFrame(() => this.animate());
}
destroy() {
cancelAnimationFrame(this.animationId);
}
}
// Usage
document.querySelectorAll('.magnetic-btn').forEach(el => {
new MagneticButton(el);
});
```
## Варианты силы притяжения
```javascript
// Слабое (для текста)
new MagneticButton(el, 0.1, 60);
// Среднее (для кнопок)
new MagneticButton(el, 0.2, 80);
// Сильное (для крупных элементов)
new MagneticButton(el, 0.3, 100);
```
## Anti-patterns
| ❌ Плохо | ✅ Хорошо |
|----------|-----------|
| `strength: 0.5` — слишком резко | `strength: 0.1–0.3` |
| Без `mouseleave` сброса | Всегда сбрасывать в 0,0 |
| Без `requestAnimationFrame` | Использовать RAF |
| На мобильных без mouse | Проверять `matchMedia('(pointer: fine)')` |
## Mobile Fallback
```javascript
if (window.matchMedia('(pointer: fine)').matches) {
document.querySelectorAll('.magnetic-btn').forEach(el => {
new MagneticButton(el);
});
}
// На мобильных — просто hover-эффект
```
## CV Service Validation
```bash
# Проверить micro-interactions
curl -s -X POST http://localhost:8001/design/analyze \
-H "Content-Type: application/json" \
-d '{"image_base64": "...", "full_analysis": false}' | python3 -c "
import sys, json
d = json.load(sys.stdin)
motion = d['dimensions'].get('motion_quality', {})
print(f\"Motion Quality: {motion.get('score', 'N/A')}/10\")
"
```
Ожидается: `motion_quality` > 6.