はじめに
正直なところ、LPやポートフォリオサイトを作るとき、アニメーションの実装には毎回悩まされていたんですよ。Framer Motionは強力だけど、一から書くのは面倒。かといって、既存のアニメーションライブラリは「それっぽい」けど微妙にダサい。
そんなときに出会ったのがMagic UI。GitHubで19,700スター以上を獲得していて、「デザインエンジニア向けのUIライブラリ」という触れ込み。実際に使ってみたら、これがかなり良かったんです。
30代になって思うのは、「見た目の良さ」と「実装の手軽さ」は両立できるということ。Magic UIはまさにその両立を実現してくれます。
実際にBorderBeamやMarquee、TypingAnimationのアニメーションを、値を変えながら触れるサンプルを用意しました。先に動きを見たい方はこちらからどうぞ。
Magic UIとは
Magic UIは、アニメーション付きのUIコンポーネントをコピー&ペーストで使えるライブラリです。公式サイトでは「UI Library for Design Engineers」と説明されています。
shadcn/uiと同じアプローチを採用していて、npmパッケージとしてインストールするのではなく、コンポーネントのソースコードを自分のプロジェクトに追加して使います。
特筆すべきは、アニメーションに特化しているという点。通常のButtonやCardではなく、Marquee(流れるテキスト)、Particles(パーティクル効果)、Globe(3D地球儀)といった、見栄えのするコンポーネントが揃っています。
- リポジトリ: magicuidesign/magicui
- スター数: 19,700以上
- ライセンス: MIT License
- コントリビューター: 129名以上
特徴・メリット
1. shadcn/ui互換のアーキテクチャ
これ、意外と重要なポイント。shadcn/uiを使っているプロジェクトなら、ほぼ同じ感覚で導入できます。
ディレクトリ構成もcomponents/ui/に配置する形式なので、既存のshadcn/uiコンポーネントと共存させやすい。技術スタックもTailwind CSS + Framer Motionで統一されています。
2. プロダクションレベルのアニメーション
個人的には、この品質が一番のメリットだと思います。
自分でアニメーションを一から作ると、どうしても「素人っぽさ」が出てしまう。イージングの調整とか、タイミングの微調整とか、地味に時間がかかるんですよね。
Magic UIのコンポーネントは、その辺りが最初から調整済み。コピペするだけで、プロが作ったようなモーションが手に入ります。
3. 50以上のコンポーネント
- テキスト系: Marquee、Typing Animation、Word Rotate、Text Reveal
- 背景系: Particles、Aurora Background、Dot Pattern、Grid Pattern
- インタラクション系: Ripple、Shine Border、Border Beam
- 3D系: Globe、Orbiting Circles
- UI系: Animated Card、Bento Grid、Dock
特にLPでよく使う「流れるロゴ一覧」とか「キラキラ光るボーダー」が簡単に実装できるのは時短になる。
4. TypeScriptで型安全
全コンポーネントがTypeScriptで書かれていて、propsの型定義もしっかりしている。補完が効くので、使い方を調べながら実装できます。
5. カスタマイズの自由度
ソースコードが手元にあるので、色やサイズ、アニメーションの速度など、自由にカスタマイズできます。気に入らない部分があれば、直接編集すればいい。
インストール方法
前提条件
- Node.js 18以降
- Reactプロジェクト(Next.js推奨)
- Tailwind CSS v3.4以上
- Framer Motion(アニメーションコンポーネントで使用)
CLIでの初期化
# npxの場合
npx magicui-cli init
# pnpmの場合
pnpm dlx magicui-cli init
このコマンドで、必要な設定ファイルとユーティリティがセットアップされます。
コンポーネントの追加
# Marqueeコンポーネントを追加
npx magicui-cli add marquee
# 複数まとめて追加
npx magicui-cli add marquee particles globe
# 特定のコンポーネントをチェック
npx magicui-cli add
追加されたコンポーネントはcomponents/magicui/ディレクトリに配置されます。
手動インストール
CLIを使わずに、公式サイトからコードをコピペすることも可能です。各コンポーネントのページに「Copy」ボタンがあるので、そこからコピーしてcomponents/magicui/に配置すればOK。
依存関係(Framer Motionなど)は別途インストールする必要があります。
npm install framer-motion clsx tailwind-merge
Magic UIのサンプルを動かす
Magic UIの代表的なコンポーネントであるBorderBeamは、実はTailwindの魔法ではなく、motion(Framer Motionの後継パッケージ)が提供するmotion.divに対して、CSSのoffset-pathとoffsetDistanceをアニメーションさせているだけの仕組みです。カード枠に沿って光の帯を1周させ、offsetDistanceを0%→100%へ変化させることで「枠を光がなぞる」ように見せています。
実際のBorderBeamコンポーネントから、アニメーションの核となる部分だけを抜き出すと次のようになります。
// Magic UIのBorderBeamのコアロジック(要点のみ)
<motion.div
style={{
position: 'absolute',
width: size,
aspectRatio: '1 / 1',
offsetPath: `rect(0 auto auto 0 round ${size}px)`,
background: `linear-gradient(90deg, ${colorFrom}, ${colorTo}, transparent)`,
}}
animate={{
offsetDistance: reverse ? ['100%', '0%'] : ['0%', '100%'],
}}
transition={{ repeat: Infinity, ease: 'linear', duration }}
/>
下のサンプルでは、速度(duration)・グラデーションの色(colorFrom/colorTo)・回転方向(reverse)をUIから変更できます。実際にBorderBeamのアニメーションを動かして体感してみてください。
durationを短くするほど光の周回が速くなり、reverseをオンにすると回転方向が逆になります。colorFrom・colorToは実際のBorderBeamコンポーネントと同じprops名で、グラデーションの始点・終点の色を指定するものです。自分のプロジェクトに導入する際も、この2色とsize(光の帯の大きさ)を調整するだけでブランドカラーに馴染ませられます。
基本的な使い方
Marquee(流れるテキスト・ロゴ)
LPでよく見る、ロゴやテキストが横に流れるやつ。
import { Marquee } from "@/components/magicui/marquee";
const logos = [
{ name: "Google", src: "/logos/google.svg" },
{ name: "Microsoft", src: "/logos/microsoft.svg" },
{ name: "Amazon", src: "/logos/amazon.svg" },
{ name: "Meta", src: "/logos/meta.svg" },
];
export function ClientLogos() {
return (
<div className="relative flex w-full flex-col items-center justify-center overflow-hidden">
<Marquee pauseOnHover className="[--duration:20s]">
{logos.map((logo) => (
<img
key={logo.name}
src={logo.src}
alt={logo.name}
className="h-12 w-auto mx-8 grayscale hover:grayscale-0 transition-all"
/>
))}
</Marquee>
</div>
);
}
--durationというCSS変数で速度を調整できます。pauseOnHoverを付けると、ホバー時に止まる。
Particles(パーティクル背景)
ヒーローセクションの背景に使うと映えるやつ。
import { Particles } from "@/components/magicui/particles";
export function HeroSection() {
return (
<div className="relative min-h-screen flex items-center justify-center">
<Particles
className="absolute inset-0"
quantity={100}
ease={80}
color="#ffffff"
refresh
/>
<div className="relative z-10 text-center">
<h1 className="text-5xl font-bold">Welcome to My Site</h1>
<p className="mt-4 text-xl text-muted-foreground">
Something amazing is coming
</p>
</div>
</div>
);
}
Border Beam(光るボーダー)
カードやボタンの周りを光が走るエフェクト。
import { BorderBeam } from "@/components/magicui/border-beam";
export function FeatureCard() {
return (
<div className="relative rounded-xl border bg-background p-6">
<BorderBeam size={250} duration={12} delay={9} />
<h3 className="text-xl font-semibold">特別な機能</h3>
<p className="mt-2 text-muted-foreground">
この機能を使うと、作業効率が3倍になります。
</p>
</div>
);
}
Typing Animation(タイピングエフェクト)
テキストがタイプライターのように表示されるエフェクト。
import { TypingAnimation } from "@/components/magicui/typing-animation";
export function HeroTitle() {
return (
<TypingAnimation
className="text-4xl font-bold"
text="エンジニアの生産性を10倍にする"
duration={100}
/>
);
}
Shine Border(輝くボーダー)
ボタンやカードに使うと高級感が出る。
import { ShineBorder } from "@/components/magicui/shine-border";
export function CTAButton() {
return (
<ShineBorder
className="text-center text-lg font-medium"
color={["#A07CFE", "#FE8FB5", "#FFBE7B"]}
>
今すぐ始める
</ShineBorder>
);
}
実践的なユースケース
LPのヒーローセクション
複数のコンポーネントを組み合わせた実践例。
import { Particles } from "@/components/magicui/particles";
import { TypingAnimation } from "@/components/magicui/typing-animation";
import { ShineBorder } from "@/components/magicui/shine-border";
import { Button } from "@/components/ui/button";
export function LandingHero() {
return (
<section className="relative min-h-screen flex items-center justify-center bg-black">
{/* 背景パーティクル */}
<Particles
className="absolute inset-0"
quantity={80}
color="#ffffff"
ease={80}
/>
{/* コンテンツ */}
<div className="relative z-10 text-center text-white px-4">
<TypingAnimation
className="text-5xl md:text-7xl font-bold mb-6"
text="未来を、今ここに。"
duration={80}
/>
<p className="text-xl md:text-2xl text-gray-300 mb-8 max-w-2xl mx-auto">
革新的なテクノロジーで、あなたのビジネスを加速させます
</p>
<div className="flex gap-4 justify-center">
<ShineBorder
className="px-8 py-3 text-lg font-semibold"
color={["#6366f1", "#8b5cf6", "#d946ef"]}
>
無料で試す
</ShineBorder>
<Button variant="outline" size="lg" className="text-white border-white hover:bg-white/10">
詳しく見る
</Button>
</div>
</div>
</section>
);
}
クライアント一覧セクション
import { Marquee } from "@/components/magicui/marquee";
const clients = [
{ name: "TechCorp", logo: "/clients/techcorp.svg" },
{ name: "StartupX", logo: "/clients/startupx.svg" },
{ name: "Enterprise Inc", logo: "/clients/enterprise.svg" },
// ... more clients
];
export function ClientsSection() {
return (
<section className="py-16 bg-muted/50">
<div className="container mx-auto px-4">
<h2 className="text-center text-sm font-semibold text-muted-foreground mb-8">
多くの企業に選ばれています
</h2>
<Marquee pauseOnHover className="[--duration:30s]">
{clients.map((client) => (
<div key={client.name} className="mx-8">
<img
src={client.logo}
alt={client.name}
className="h-8 w-auto opacity-50 hover:opacity-100 transition-opacity"
/>
</div>
))}
</Marquee>
</div>
</section>
);
}
機能紹介カード
import { BorderBeam } from "@/components/magicui/border-beam";
import { MagicCard } from "@/components/magicui/magic-card";
const features = [
{
title: "高速処理",
description: "最新の技術で、従来の10倍の速度を実現",
icon: "⚡",
},
{
title: "セキュリティ",
description: "エンタープライズグレードのセキュリティ",
icon: "🔒",
},
{
title: "スケーラブル",
description: "成長に合わせて柔軟に拡張可能",
icon: "📈",
},
];
export function FeaturesSection() {
return (
<section className="py-20">
<div className="container mx-auto px-4">
<h2 className="text-3xl font-bold text-center mb-12">
選ばれる3つの理由
</h2>
<div className="grid md:grid-cols-3 gap-8">
{features.map((feature, index) => (
<div
key={feature.title}
className="relative rounded-xl border bg-background p-8"
>
<BorderBeam size={200} duration={15} delay={index * 3} />
<div className="text-4xl mb-4">{feature.icon}</div>
<h3 className="text-xl font-semibold mb-2">{feature.title}</h3>
<p className="text-muted-foreground">{feature.description}</p>
</div>
))}
</div>
</div>
</section>
);
}
MarqueeをCSSキーフレームだけで動かす
「基本的な使い方」で紹介したMarqueeは、実はFramer Motionを使わず、CSSの@keyframesだけで実装されています。中身のコンテンツを複製して並べ、animation: marquee var(--duration) linear infiniteのようなアニメーションを当てているだけの仕組みです。pauseOnHoverはanimation-play-state: pausedを、reverseはanimation-direction: reverseを切り替えているにすぎません。
要点を抜き出すと次のようになります。
/* Magic UIのMarqueeが使っているキーフレーム(要約) */
@keyframes marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
.marquee-track {
animation: marquee var(--duration) linear infinite;
}
.marquee-track:hover {
animation-play-state: paused; /* pauseOnHover */
}
下のサンプルでは、速度(duration)・ホバー時の一時停止(pauseOnHover)・進行方向(reverse)をその場で切り替えられます。Marqueeがロゴやテキストを横に流し続ける様子を確認してみてください。
マウスを乗せるとpauseOnHoverの効果で流れが止まり、チェックを外すと止まらなくなります。durationを小さくするほど速く流れ、reverseをオンにすると流れる向きが逆転します。実際のプロジェクトではitemsをロゴ画像の配列に差し替えるだけで、そのままクライアントロゴ一覧に転用できます。
TypingAnimationの状態遷移ロジックを試す
TypingAnimationは1単語だけでなく、複数の単語を渡すと「タイプ→一時停止→削除→次の単語」を自動でループする作りになっています。この裏側ではtyping・pause・deletingという3つのフェーズをsetTimeoutで切り替える状態遷移ロジックが動いています。
コアとなる状態遷移だけを抜き出すと、次のようになります。
// TypingAnimationの状態遷移(要約)
switch (phase) {
case 'typing':
if (charIndex < word.length) {
setText(word.slice(0, charIndex + 1))
setCharIndex(charIndex + 1)
} else {
setPhase('pause')
}
break
case 'pause':
setPhase('deleting')
break
case 'deleting':
if (charIndex > 0) {
setText(word.slice(0, charIndex - 1))
setCharIndex(charIndex - 1)
} else {
setWordIndex((wordIndex + 1) % words.length)
setPhase('typing')
}
break
}
下のサンプルでは、タイプさせる単語(words、カンマ区切り)とタイプ速度(typeSpeed)をUIから変更できます。文字を打ち終えると自動で削除され、次の単語のタイプが始まります。
wordsに複数の単語をカンマ区切りで入れると、1つ目を打ち終えたあとに自動で削除して次の単語に切り替わります。typeSpeedを小さくするほどタイプが速くなり、これは実際のTypingAnimationコンポーネントのduration(typeSpeed)propと同じ役割です。
shadcn/uiとの使い分け
個人的には、こう使い分けています。
| 用途 | 使うライブラリ |
|---|---|
| フォーム、ダイアログ、テーブル | shadcn/ui |
| ヒーローセクション、背景効果 | Magic UI |
| ボタン、カード(静的) | shadcn/ui |
| ボタン、カード(装飾的) | Magic UI |
| 管理画面のUI | shadcn/ui |
| LP、ポートフォリオ | Magic UI |
コスパ的に、両方入れておいて使い分けるのがベスト。競合しないので共存できます。
注意点
パフォーマンス
アニメーションは見栄えがする一方で、パフォーマンスへの影響もあります。特にParticlesやGlobeは描画負荷が高いので、モバイル端末では控えめに使った方がいい。
// モバイルでは軽量版にする例
const isMobile = typeof window !== "undefined" && window.innerWidth < 768;
<Particles quantity={isMobile ? 30 : 100} />
アクセシビリティ
アニメーションが苦手な人もいるので、prefers-reduced-motionへの対応は忘れずに。Magic UIのコンポーネントは基本的に対応していますが、カスタマイズ時は注意。
まとめ
Magic UIを導入して感じた変化:
- LP制作の時短: アニメーション実装にかける時間が大幅に減った
- クオリティ向上: プロっぽいモーションが簡単に実現できる
- 学習コスト低: shadcn/uiと同じ感覚で使える
- 自由度: ソースが手元にあるのでカスタマイズし放題
30代になって思うのは、「自分で一から作る」ことに価値があるわけではないということ。良いものは借りて、自分はビジネスロジックに集中する。Magic UIはその考え方にぴったりのライブラリです。
特にLP制作やポートフォリオサイトを作る機会がある人には、かなりおすすめ。shadcn/uiと併用すれば、見た目も機能も妥協しないサイトが作れますよ。