なぜ作ったか
CSS の box-shadow はカンマ区切りで複数レイヤを重ねられるのが強力なのに、既存のジェネレータは 1 レイヤしか扱えないものが多い。Neumorphism は 2 レイヤ、Layered depth は 3 レイヤ必要で、1 レイヤ編集ツールでは設計できません。
そこで全レイヤを同時に表示・編集できるツールを作りました。
作ったもの
Shadow Designer — https://sen.ltd/portfolio/shadow-designer/
- 複数シャドウレイヤ対応(追加・削除)
- レイヤごとに offsetX / offsetY / blur / spread / 色 / inset を調整
- 5 プリセット: Soft / Neumorphism / Glassmorphism / Layered / Brutalist
- ライブプレビュー(ボックスのサイズ・角丸・背景色もカスタマイズ可)
- CSS をワンクリックコピー
- 日本語 / 英語、ダーク / ライトモード
vanilla JS、ゼロ依存、ビルド不要。node --test で 21 ケース。
コアロジック: レイヤ配列 → CSS 文字列
各レイヤは素朴なオブジェクト:
{
offsetX: 4, // px
offsetY: 4, // px
blur: 8, // px (≥ 0)
spread: 0, // px
color: '#000000',
inset: false,
}
1 レイヤの CSS 変換:
export function layerToCSS(layer) {
const { offsetX = 0, offsetY = 0, blur = 0, spread = 0,
color = '#000000', inset = false } = layer;
const parts = inset ? ['inset'] : [];
parts.push(`${offsetX}px`, `${offsetY}px`, `${blur}px`, `${spread}px`, color);
return parts.join(' ');
}
全体は layers.map(layerToCSS).join(', ')。これがレンダリングパイプラインの全て。スライダやプリセットは「配列を操作してこの関数を呼ぶ」だけ。
Neumorphism は「2 レイヤで浮き彫り」
layers: [
{ offsetX: 6, offsetY: 6, blur: 12, color: 'rgba(0,0,0,0.20)' },
{ offsetX: -6, offsetY: -6, blur: 12, color: 'rgba(255,255,255,0.70)' },
]
左上から光、右下に暗い影。2 つ重ねるとエンボス(浮き彫り)効果。1 レイヤだけではただのドロップシャドウ。
Glassmorphism は inset を使う
layers: [
{ offsetX: 0, offsetY: 8, blur: 32, color: 'rgba(255,255,255,0.15)' },
{ offsetX: 0, offsetY: 0, blur: 0, spread: 1, color: 'rgba(255,255,255,0.30)', inset: true },
]
inset + blur: 0, spread: 1 で 1px の内側ボーダーを作る。backdrop-filter: blur() と組み合わせるとすりガラス風。
Layered depth: 3 段重ね
layers: [
{ offsetY: 1, blur: 2, color: 'rgba(0,0,0,0.07)' },
{ offsetY: 4, blur: 8, color: 'rgba(0,0,0,0.07)' },
{ offsetY: 12, blur: 24, color: 'rgba(0,0,0,0.07)' },
]
近い影 → 接地感、中 → 浮遊感、遠い → 環境光。低い不透明度を 3 段重ねると、単一の重い影よりずっと自然に見えます。
テスト
node --test で 21 ケース:
test('Neumorphism preset has exactly 2 layers', () => {
const layers = parsePreset('Neumorphism');
assert.strictEqual(layers.length, 2);
});
test('Brutalist preset has blur 0 on all layers', () => {
const layers = parsePreset('Brutalist');
assert.ok(layers.every((l) => l.blur === 0));
});
プリセットの構造テストは「ドキュメントとしてのテスト」。読むだけで各プリセットの特徴がわかります。
シリーズ
100+ 公開ポートフォリオ シリーズの #34 です。
