241 lines
5.9 KiB
Markdown
241 lines
5.9 KiB
Markdown
---
|
||
name: system/noise-texture
|
||
title: Noise & Grain Texture Overlay
|
||
description: Subtle noise/grain overlay for premium tactile feel. Eliminates flat digital look.
|
||
tags: [system, noise, texture, grain, tactile, premium]
|
||
source: stripe.com, linear.app, brad.co, amppz.com
|
||
---
|
||
|
||
# Noise & Grain Texture Overlay
|
||
|
||
## Что это
|
||
Тонкая текстура шума (noise), наложенная поверх фона. Создаёт ощущение тактильности и устраняет "плоский" цифровой вид. Используется на Stripe, Linear, Branded.
|
||
|
||
## Почему это важно
|
||
Без текстуры тёмный сайт выглядит плоским. Noise добавляет:
|
||
- Глубину
|
||
- Тактильность
|
||
- "Дорогой" вид
|
||
- Маскировку градиентных артефактов
|
||
|
||
## Pattern 1: CSS Noise (рекомендуется)
|
||
|
||
```html
|
||
<div class="noise-overlay"></div>
|
||
```
|
||
|
||
```css
|
||
.noise-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
pointer-events: none;
|
||
z-index: 9999;
|
||
opacity: 0.03;
|
||
mix-blend-mode: overlay;
|
||
background-image: url('/noise.png');
|
||
background-size: 256px 256px;
|
||
}
|
||
```
|
||
|
||
### Генерация noise.png
|
||
```bash
|
||
# Python — генерация noise текстуры
|
||
python3 -c "
|
||
import numpy as np
|
||
from PIL import Image
|
||
noise = np.random.randint(0, 255, (512, 512, 3), dtype=np.uint8)
|
||
Image.fromarray(noise).save('/public/noise.png')
|
||
"
|
||
|
||
# Или через CLI
|
||
convert -size 256x256 plasma:random /public/noise.png
|
||
```
|
||
|
||
## Pattern 2: SVG Noise Filter
|
||
|
||
```html
|
||
<!-- SVG Noise Filter — не требует отдельного файла -->
|
||
<svg style="position: absolute; width: 0; height: 0; overflow: hidden;">
|
||
<defs>
|
||
<filter id="noise">
|
||
<feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/>
|
||
<feColorMatrix type="saturate" values="0"/>
|
||
</filter>
|
||
</defs>
|
||
</svg>
|
||
```
|
||
|
||
```css
|
||
.noise-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
pointer-events: none;
|
||
z-index: 9999;
|
||
opacity: 0.04;
|
||
filter: url(#noise);
|
||
mix-blend-mode: overlay;
|
||
}
|
||
```
|
||
|
||
## Pattern 3: CSS Radial Noise (без изображений)
|
||
|
||
```css
|
||
.noise-css {
|
||
position: fixed;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
z-index: 9999;
|
||
opacity: 0.03;
|
||
background:
|
||
radial-gradient(circle, #fff 1px, transparent 1px),
|
||
radial-gradient(circle, #fff 1px, transparent 1px);
|
||
background-size: 20px 20px;
|
||
background-position: 0 0, 10px 10px;
|
||
mix-blend-mode: overlay;
|
||
animation: noise-shift 0.5s steps(1) infinite;
|
||
}
|
||
|
||
@keyframes noise-shift {
|
||
0% { transform: translate(0, 0); }
|
||
25% { transform: translate(-5px, 5px); }
|
||
50% { transform: translate(5px, -5px); }
|
||
75% { transform: translate(-5px, -5px); }
|
||
100% { transform: translate(5px, 5px); }
|
||
}
|
||
```
|
||
|
||
## Pattern 4: Gradient Noise Mix
|
||
|
||
```css
|
||
.gradient-noise {
|
||
position: relative;
|
||
}
|
||
|
||
.gradient-noise::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
background:
|
||
linear-gradient(135deg, rgba(99, 102, 241, 0.1), transparent),
|
||
url('/noise.png');
|
||
background-size: cover, 256px 256px;
|
||
opacity: 0.8;
|
||
pointer-events: none;
|
||
}
|
||
```
|
||
|
||
## Pattern 5: Noise on Cards/Sections
|
||
|
||
```html
|
||
<section class="card-with-noise">
|
||
<div class="noise-layer"></div>
|
||
<div class="card-content">
|
||
<h3>Контент карточки</h3>
|
||
<p>Текст карточки...</p>
|
||
</div>
|
||
</section>
|
||
```
|
||
|
||
```css
|
||
.card-with-noise {
|
||
position: relative;
|
||
background: var(--color-surface);
|
||
border: 1px solid var(--color-border);
|
||
border-radius: 20px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.noise-layer {
|
||
position: absolute;
|
||
inset: 0;
|
||
opacity: 0.02;
|
||
background-image: url('/noise.png');
|
||
background-size: 256px 256px;
|
||
pointer-events: none;
|
||
mix-blend-mode: overlay;
|
||
}
|
||
|
||
.card-content {
|
||
position: relative;
|
||
z-index: 1;
|
||
padding: 48px;
|
||
}
|
||
```
|
||
|
||
## Noise Intensity Levels
|
||
|
||
| Уровень | Opacity | Использование |
|
||
|---------|---------|---------------|
|
||
| `0.01` | Очень тихо | Фоновый шум на светлых сайтах |
|
||
| `0.02–0.03` | Тихо | Тёмные фоны (стандарт) |
|
||
| `0.04–0.06` | Средне | Градиенты, акцентные элементы |
|
||
| `0.07+` | Громко | Текстуры, арт-проекты |
|
||
|
||
## Anti-patterns
|
||
|
||
| ❌ Плохо | ✅ Хорошо |
|
||
|----------|-----------|
|
||
| `opacity: 0.3` — слишком видно | `opacity: 0.02–0.04` |
|
||
| `mix-blend-mode: normal` | `mix-blend-mode: overlay` |
|
||
| Без `pointer-events: none` | `pointer-events: none` |
|
||
| Без `z-index: 9999` | Всегда поверх контента |
|
||
| Тяжёлый PNG (512x512+) | `256x256` оптимизированный |
|
||
|
||
## Performance
|
||
|
||
```css
|
||
/* ✅ Good — CSS-only noise */
|
||
.noise-css {
|
||
background:
|
||
radial-gradient(circle, #fff 1px, transparent 1px);
|
||
/* GPU accelerated */
|
||
}
|
||
|
||
/* ✅ Good — оптимизированный PNG */
|
||
.noise-overlay {
|
||
background-image: url('/noise-256.png');
|
||
/* 256x256, < 5KB */
|
||
}
|
||
|
||
/* ❌ Bad — тяжёлый SVG */
|
||
.noise-overlay {
|
||
filter: url('#noise-filter');
|
||
/* Перерисовка всего viewport */
|
||
}
|
||
```
|
||
|
||
## Browser Support
|
||
|
||
```css
|
||
.noise-overlay {
|
||
background-image: url('/noise.png'); /* fallback */
|
||
background-image: url('/noise.svg'); /* modern */
|
||
/* Для очень старых браузеров */
|
||
@supports (background: url('/noise.png')) {
|
||
/* стандартное поведение */
|
||
}
|
||
}
|
||
```
|
||
|
||
## CV Service Validation
|
||
|
||
```bash
|
||
# Проверить visual hierarchy с noise
|
||
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)
|
||
color = d['dimensions'].get('color_harmony', {})
|
||
print(f\"Color Harmony: {color.get('score', 'N/A')}/10\")
|
||
"
|
||
```
|
||
|
||
Ожидается: `color_harmony` > 6 (noise улучшает восприятие цвета).
|