4.0 KiB
4.0 KiB
name, title, description, tags, source
| name | title | description | tags | source | |||||
|---|---|---|---|---|---|---|---|---|---|
| motion/magnetic-button | Magnetic Button Effect | Buttons that follow cursor within radius. Creates premium tactile feel. |
|
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 |
Реализация
<button class="magnetic-btn">Hover me</button>
.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);
}
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);
});
Варианты силы притяжения
// Слабое (для текста)
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
if (window.matchMedia('(pointer: fine)').matches) {
document.querySelectorAll('.magnetic-btn').forEach(el => {
new MagneticButton(el);
});
}
// На мобильных — просто hover-эффект
CV Service Validation
# Проверить 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.