React で SVG アニメーションを自在に操る!2025 年最新サンプル集

近年、Webアプリケーションのユーザー体験は劇的に向上しており、その中核を担うのがアニメーションです。特にSVGアニメーションは、ベクター形式の美しさとインタラクティブ性を兼ね備え、モダンなWebサイトには欠かせない要素となりました。
Reactと組み合わせることで、コンポーネント指向の開発スタイルでSVGアニメーションを効率的に実装できるのですが、実際に取り組んでみると「思ったより複雑だな」と感じる開発者の方も多いのではないでしょうか。本記事では、そんな皆様の悩みを解決する具体的な手法とサンプルコードをご紹介いたします。
背景
SVGアニメーションの重要性
現代のWebデザインにおいて、SVGアニメーションは単なる装飾ではありません。ユーザーの注意を適切に誘導し、操作に対するフィードバックを提供し、ブランドの印象を強化する重要な役割を担っています。
特に2025年現在では、ユーザーはより洗練された体験を求めており、静的なUIでは物足りなさを感じるようになっています。Google Material Designの進化やAppleのHuman Interface Guidelinesの更新を見ても、アニメーションが如何に重要視されているかがわかりますね。
React でのSVG活用メリット
ReactでSVGを扱う最大のメリットは、コンポーネント化による再利用性です。従来のHTMLベースのアニメーションと比較して、以下のような利点があります。
特徴 | React SVG | 従来のHTML/CSS |
---|---|---|
再利用性 | ✅ コンポーネントとして管理 | ❌ 個別に実装が必要 |
状態管理 | ✅ Reactの状態と連動 | ❌ 別途JavaScriptが必要 |
TypeScript対応 | ✅ 型安全性を確保 | ❌ 型チェックが困難 |
バンドルサイズ | ✅ 最適化しやすい | ❌ 重複が発生しやすい |
これらの特徴により、開発効率とメンテナンス性が大幅に向上するのです。
2025年のSVGアニメーション動向
2025年現在のトレンドとして、以下のような傾向が見られます。
パフォーマンスファーストの考え方が浸透し、60FPSを維持しながらもバッテリー消費を抑えたアニメーションが求められています。また、アクセシビリティへの配慮も必須となり、prefers-reduced-motion
への対応は標準的な実装となっています。
さらに、WebAssemblyとの連携による高度なアニメーション表現や、PWAでのネイティブアプリライクな体験も注目されているところです。
課題
React でのSVGアニメーション実装の難しさ
ReactでSVGアニメーションを実装する際、多くの開発者が直面する課題があります。まず、DOMの直接操作とReactの仮想DOMの競合です。
javascript// ❌ よくある間違い - 直接DOM操作
useEffect(() => {
const element = document.getElementById('my-svg');
element.style.transform = 'rotate(45deg)'; // Reactの管理外
}, []);
このようなコードは、Reactの状態管理と矛盾を生じさせ、予期しない動作の原因となります。
また、ライフサイクルとアニメーションタイミングの調整も厄介な問題です。コンポーネントのマウント・アンマウント時にアニメーションが中途半端な状態で止まってしまうことがあります。
vbnet// よく遭遇するエラー
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
パフォーマンスとの両立
SVGアニメーションで最も頭を悩ませるのが、滑らかな動作とパフォーマンスの両立です。特に以下のような問題が頻発します。
レンダリング負荷の問題として、複雑なSVGパスや多数の要素をアニメーションさせると、フレームレートが低下してしまいます。ブラウザの開発者ツールで確認すると、以下のような警告が表示されることもあります。
csharp[Violation] 'requestAnimationFrame' handler took 23ms
[Violation] Forced reflow while executing JavaScript took 45ms
メモリリークの懸念も深刻です。アニメーションが終了してもイベントリスナーやタイマーが残存し、メモリ使用量が増加し続ける場合があります。
ブラウザ対応とアクセシビリティ
モダンブラウザではSVGアニメーションは良好に動作しますが、古いブラウザでの互換性やアクセシビリティ対応には注意が必要です。
特に、視覚に障害のある方や前庭機能障害のある方への配慮として、prefers-reduced-motion
メディアクエリへの対応は必須となっています。しかし、単純にアニメーションを無効化するだけでは、情報の伝達に支障をきたす場合もあります。
解決策
これらの課題を解決するため、実際のプロジェクトで効果的な4つのアプローチをご紹介いたします。
React-Spring を使ったアニメーション
React-Springは、物理法則に基づいた自然なアニメーションを簡単に実装できるライブラリです。以下のように直感的にSVGアニメーションを作成できます。
まず、必要なパッケージをインストールします:
bashyarn add @react-spring/web
基本的な回転アニメーションを実装してみましょう:
typescriptimport { useSpring, animated } from '@react-spring/web';
interface RotatingIconProps {
isActive: boolean;
}
const RotatingIcon: React.FC<RotatingIconProps> = ({ isActive }) => {
// スプリングアニメーションの設定
const styles = useSpring({
transform: isActive ? 'rotate(180deg)' : 'rotate(0deg)',
config: { tension: 300, friction: 30 }
});
return (
<animated.svg
width="50"
height="50"
style={styles}
viewBox="0 0 50 50"
>
<path
d="M25 5 L45 25 L25 45 L5 25 Z"
fill="#3b82f6"
stroke="#1e40af"
strokeWidth="2"
/>
</animated.svg>
);
};
この実装では、isActive
の状態に応じて滑らかに回転アニメーションが実行されます。tension
とfriction
のパラメータで、アニメーションの「弾力性」を調整できるのが特徴です。
より複雑なパスアニメーションも簡単に実装できます:
typescriptconst AnimatedPath: React.FC = () => {
const [toggle, setToggle] = useState(false);
const pathAnimation = useSpring({
pathLength: toggle ? 1 : 0,
config: { duration: 2000 }
});
return (
<svg width="200" height="100" onClick={() => setToggle(!toggle)}>
<animated.path
d="M10,50 Q100,10 190,50"
stroke="#ef4444"
strokeWidth="3"
fill="none"
strokeDasharray="200"
strokeDashoffset={pathAnimation.pathLength.to(
length => 200 * (1 - length)
)}
/>
</svg>
);
};
Framer Motion でのSVG制御
Framer Motionは、宣言的な記述でアニメーションを定義できる優秀なライブラリです。特にSVGアニメーションにおいて、その真価を発揮します。
bashyarn add framer-motion
SVGパスの描画アニメーションを実装してみましょう:
typescriptimport { motion } from 'framer-motion';
const DrawingAnimation: React.FC = () => {
const draw = {
hidden: { pathLength: 0, opacity: 0 },
visible: (i: number) => {
const delay = 0.5 + i * 0.5;
return {
pathLength: 1,
opacity: 1,
transition: {
pathLength: { delay, type: "spring", duration: 1.5, bounce: 0 },
opacity: { delay, duration: 0.01 }
}
};
}
};
return (
<motion.svg
width="400"
height="400"
viewBox="0 0 400 400"
initial="hidden"
animate="visible"
>
<motion.circle
cx="200"
cy="200"
r="80"
stroke="#8b5cf6"
strokeWidth="4"
fill="transparent"
custom={0}
variants={draw}
/>
<motion.path
d="M200,120 L200,280 M120,200 L280,200"
stroke="#8b5cf6"
strokeWidth="4"
custom={1}
variants={draw}
/>
</motion.svg>
);
};
このコードでは、円とクロス線が順番に描画されるアニメーションを実装しています。custom
プロパティを使って、各要素のアニメーション開始タイミングを制御している点が重要です。
ホバー効果と組み合わせた実用的な例もご紹介します:
typescriptconst InteractiveButton: React.FC = () => {
return (
<motion.svg
width="120"
height="40"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
>
<motion.rect
x="5"
y="5"
width="110"
height="30"
rx="15"
fill="#10b981"
whileHover={{ fill: "#059669" }}
transition={{ duration: 0.2 }}
/>
<motion.text
x="60"
y="24"
textAnchor="middle"
fill="white"
fontSize="14"
whileHover={{ y: 22 }}
>
クリック
</motion.text>
</motion.svg>
);
};
CSS-in-JS でのアニメーション管理
CSS-in-JSを使ったアプローチでは、Reactコンポーネントの状態とCSSアニメーションを密結合できます。styled-componentsやEmotionを使った実装例をご紹介します。
bashyarn add styled-components @types/styled-components
まず、基本的なパルスアニメーションから始めましょう:
typescriptimport styled, { keyframes } from 'styled-components';
const pulse = keyframes`
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.2); opacity: 0.7; }
100% { transform: scale(1); opacity: 1; }
`;
const PulsingIcon = styled.svg<{ isActive: boolean }>`
animation: ${props => props.isActive ? pulse : 'none'} 2s infinite;
transition: all 0.3s ease;
&:hover {
filter: drop-shadow(0 0 8px rgba(59, 130, 246, 0.5));
}
`;
const LoadingIndicator: React.FC<{ isLoading: boolean }> = ({ isLoading }) => {
return (
<PulsingIcon
width="60"
height="60"
viewBox="0 0 60 60"
isActive={isLoading}
>
<circle
cx="30"
cy="30"
r="25"
fill="#3b82f6"
stroke="#1e40af"
strokeWidth="2"
/>
</PulsingIcon>
);
};
より複雑なアニメーション状態を管理する場合は、以下のようにenumを活用すると良いでしょう:
typescriptenum AnimationState {
IDLE = 'idle',
LOADING = 'loading',
SUCCESS = 'success',
ERROR = 'error'
}
const getAnimationCSS = (state: AnimationState) => {
switch (state) {
case AnimationState.LOADING:
return css`
animation: ${pulse} 1.5s infinite;
fill: #3b82f6;
`;
case AnimationState.SUCCESS:
return css`
animation: ${pulse} 0.5s ease-out;
fill: #10b981;
`;
case AnimationState.ERROR:
return css`
animation: shake 0.5s ease-out;
fill: #ef4444;
`;
default:
return css`
fill: #6b7280;
`;
}
};
useRef とuseEffect を活用した手動制御
より細かい制御が必要な場合は、useRefとuseEffectを組み合わせたアプローチが効果的です。Web Animations APIを使った実装例をご紹介します。
typescriptimport { useRef, useEffect, useCallback } from 'react';
interface AnimationOptions {
duration: number;
easing: string;
iterations: number;
}
const useManualAnimation = () => {
const elementRef = useRef<SVGElement>(null);
const animationRef = useRef<Animation | null>(null);
const startAnimation = useCallback((
keyframes: Keyframe[],
options: AnimationOptions
) => {
if (!elementRef.current) return;
// 既存のアニメーションを停止
if (animationRef.current) {
animationRef.current.cancel();
}
// 新しいアニメーションを開始
animationRef.current = elementRef.current.animate(
keyframes,
options
);
return animationRef.current;
}, []);
const stopAnimation = useCallback(() => {
if (animationRef.current) {
animationRef.current.cancel();
animationRef.current = null;
}
}, []);
// クリーンアップ
useEffect(() => {
return () => {
stopAnimation();
};
}, [stopAnimation]);
return { elementRef, startAnimation, stopAnimation };
};
このカスタムフックを使って、実際のSVGアニメーションを実装してみましょう:
typescriptconst ManualAnimatedIcon: React.FC = () => {
const { elementRef, startAnimation, stopAnimation } = useManualAnimation();
const [isAnimating, setIsAnimating] = useState(false);
const handleClick = useCallback(() => {
if (isAnimating) {
stopAnimation();
setIsAnimating(false);
} else {
const animation = startAnimation(
[
{ transform: 'rotate(0deg) scale(1)' },
{ transform: 'rotate(180deg) scale(1.2)' },
{ transform: 'rotate(360deg) scale(1)' }
],
{
duration: 1000,
easing: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
iterations: 1
}
);
if (animation) {
setIsAnimating(true);
animation.addEventListener('finish', () => {
setIsAnimating(false);
});
}
}
}, [isAnimating, startAnimation, stopAnimation]);
return (
<svg
ref={elementRef}
width="80"
height="80"
viewBox="0 0 80 80"
onClick={handleClick}
style={{ cursor: 'pointer' }}
>
<rect
x="20"
y="20"
width="40"
height="40"
rx="8"
fill="#f59e0b"
stroke="#d97706"
strokeWidth="2"
/>
</svg>
);
};
具体例
理論だけでは実感が湧かないと思いますので、実際のプロジェクトでよく使われる具体的なサンプルをご紹介いたします。
ローディングアニメーション
ローディングアニメーションは、ユーザーに待機時間を意識させないための重要なUI要素です。以下のような回転するリングアニメーションを実装してみましょう:
typescriptimport { useSpring, animated } from '@react-spring/web';
const LoadingSpinner: React.FC<{ size?: number }> = ({ size = 40 }) => {
const rotateAnimation = useSpring({
from: { transform: 'rotate(0deg)' },
to: { transform: 'rotate(360deg)' },
config: { duration: 1000 },
loop: true
});
return (
<animated.svg
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`}
style={rotateAnimation}
>
<circle
cx={size / 2}
cy={size / 2}
r={(size - 8) / 2}
stroke="#e5e7eb"
strokeWidth="4"
fill="none"
/>
<circle
cx={size / 2}
cy={size / 2}
r={(size - 8) / 2}
stroke="#3b82f6"
strokeWidth="4"
fill="none"
strokeLinecap="round"
strokeDasharray={`${Math.PI * (size - 8) * 0.75} ${Math.PI * (size - 8)}`}
/>
</animated.svg>
);
};
プログレスバー付きのローディングも実装してみましょう:
typescriptinterface ProgressLoaderProps {
progress: number; // 0-100
message?: string;
}
const ProgressLoader: React.FC<ProgressLoaderProps> = ({
progress,
message = "読み込み中..."
}) => {
const progressAnimation = useSpring({
strokeDashoffset: 283 - (283 * progress) / 100,
config: { tension: 200, friction: 20 }
});
return (
<div style={{ textAlign: 'center' }}>
<svg width="100" height="100" viewBox="0 0 100 100">
<circle
cx="50"
cy="50"
r="45"
stroke="#e5e7eb"
strokeWidth="8"
fill="none"
/>
<animated.circle
cx="50"
cy="50"
r="45"
stroke="#10b981"
strokeWidth="8"
fill="none"
strokeLinecap="round"
strokeDasharray="283"
strokeDashoffset={progressAnimation.strokeDashoffset}
transform="rotate(-90 50 50)"
/>
<text
x="50"
y="55"
textAnchor="middle"
fontSize="14"
fill="#374151"
>
{Math.round(progress)}%
</text>
</svg>
<p style={{ margin: '10px 0', color: '#6b7280' }}>{message}</p>
</div>
);
};
インタラクティブなアイコン
ユーザーの操作に反応するアイコンは、UIの直感性を大幅に向上させます。ハンバーガーメニューの変形アニメーションを実装してみましょう:
typescriptimport { motion } from 'framer-motion';
interface HamburgerMenuProps {
isOpen: boolean;
onClick: () => void;
}
const HamburgerMenu: React.FC<HamburgerMenuProps> = ({ isOpen, onClick }) => {
return (
<motion.svg
width="30"
height="30"
viewBox="0 0 30 30"
onClick={onClick}
style={{ cursor: 'pointer' }}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
<motion.line
x1="5"
y1="8"
x2="25"
y2="8"
stroke="#374151"
strokeWidth="2"
strokeLinecap="round"
animate={{
rotate: isOpen ? 45 : 0,
y: isOpen ? 6 : 0
}}
transition={{ duration: 0.3 }}
/>
<motion.line
x1="5"
y1="15"
x2="25"
y2="15"
stroke="#374151"
strokeWidth="2"
strokeLinecap="round"
animate={{
opacity: isOpen ? 0 : 1
}}
transition={{ duration: 0.3 }}
/>
<motion.line
x1="5"
y1="22"
x2="25"
y2="22"
stroke="#374151"
strokeWidth="2"
strokeLinecap="round"
animate={{
rotate: isOpen ? -45 : 0,
y: isOpen ? -6 : 0
}}
transition={{ duration: 0.3 }}
/>
</motion.svg>
);
};
いいね機能のハートアニメーションも実装してみましょう:
typescriptconst LikeButton: React.FC = () => {
const [isLiked, setIsLiked] = useState(false);
const [count, setCount] = useState(42);
const heartVariants = {
liked: {
scale: [1, 1.3, 1],
fill: "#ef4444",
transition: { duration: 0.3 }
},
unliked: {
scale: 1,
fill: "#9ca3af",
transition: { duration: 0.3 }
}
};
const handleLike = () => {
setIsLiked(!isLiked);
setCount(prev => isLiked ? prev - 1 : prev + 1);
};
return (
<motion.div
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
whileHover={{ scale: 1.05 }}
>
<motion.svg
width="24"
height="24"
viewBox="0 0 24 24"
onClick={handleLike}
style={{ cursor: 'pointer' }}
>
<motion.path
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
variants={heartVariants}
animate={isLiked ? "liked" : "unliked"}
stroke={isLiked ? "#ef4444" : "#9ca3af"}
strokeWidth="1"
/>
</motion.svg>
<motion.span
style={{ fontSize: '14px', color: '#374151' }}
key={count}
initial={{ scale: 1 }}
animate={{ scale: [1, 1.2, 1] }}
transition={{ duration: 0.2 }}
>
{count}
</motion.span>
</motion.div>
);
};
データビジュアライゼーション
データの可視化では、アニメーションによって情報の変化を直感的に理解させることができます。アニメーション付きの棒グラフを実装してみましょう:
typescriptinterface DataPoint {
label: string;
value: number;
color: string;
}
interface AnimatedBarChartProps {
data: DataPoint[];
width?: number;
height?: number;
}
const AnimatedBarChart: React.FC<AnimatedBarChartProps> = ({
data,
width = 400,
height = 300
}) => {
const maxValue = Math.max(...data.map(d => d.value));
const barWidth = (width - 100) / data.length - 20;
return (
<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
{/* Y軸のグリッドライン */}
{[0, 0.25, 0.5, 0.75, 1].map((ratio, index) => (
<g key={index}>
<line
x1="50"
y1={50 + (height - 100) * ratio}
x2={width - 20}
y2={50 + (height - 100) * ratio}
stroke="#e5e7eb"
strokeWidth="1"
/>
<text
x="40"
y={55 + (height - 100) * ratio}
textAnchor="end"
fontSize="12"
fill="#6b7280"
>
{Math.round(maxValue * (1 - ratio))}
</text>
</g>
))}
{/* バーとラベル */}
{data.map((item, index) => {
const barHeight = ((height - 100) * item.value) / maxValue;
const x = 60 + index * (barWidth + 20);
return (
<g key={item.label}>
<motion.rect
x={x}
y={height - 50}
width={barWidth}
height={0}
fill={item.color}
animate={{
height: barHeight,
y: height - 50 - barHeight
}}
transition={{
duration: 1,
delay: index * 0.1,
ease: "easeOut"
}}
/>
<text
x={x + barWidth / 2}
y={height - 30}
textAnchor="middle"
fontSize="12"
fill="#374151"
>
{item.label}
</text>
<motion.text
x={x + barWidth / 2}
y={height - 55 - barHeight}
textAnchor="middle"
fontSize="11"
fill="#374151"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: index * 0.1 + 0.5 }}
>
{item.value}
</motion.text>
</g>
);
})}
</svg>
);
};
// 使用例
const ChartExample: React.FC = () => {
const [data, setData] = useState<DataPoint[]>([
{ label: 'Jan', value: 65, color: '#3b82f6' },
{ label: 'Feb', value: 78, color: '#10b981' },
{ label: 'Mar', value: 52, color: '#f59e0b' },
{ label: 'Apr', value: 91, color: '#ef4444' }
]);
const updateData = () => {
setData(prev => prev.map(item => ({
...item,
value: Math.floor(Math.random() * 100) + 10
})));
};
return (
<div>
<AnimatedBarChart data={data} />
<button onClick={updateData} style={{ marginTop: '20px' }}>
データを更新
</button>
</div>
);
};
ページトランジション効果
ページ遷移時のアニメーションは、ユーザーの操作に連続性を与え、アプリケーション全体の品質を向上させます。
typescriptimport { AnimatePresence, motion } from 'framer-motion';
import { useState } from 'react';
interface Page {
id: string;
title: string;
content: string;
color: string;
}
const PageTransition: React.FC = () => {
const [currentPageIndex, setCurrentPageIndex] = useState(0);
const pages: Page[] = [
{ id: 'home', title: 'ホーム', content: 'ホームページの内容', color: '#3b82f6' },
{ id: 'about', title: '概要', content: '概要ページの内容', color: '#10b981' },
{ id: 'contact', title: '連絡先', content: '連絡先ページの内容', color: '#f59e0b' }
];
const pageVariants = {
initial: {
opacity: 0,
x: 300,
scale: 0.8
},
in: {
opacity: 1,
x: 0,
scale: 1
},
out: {
opacity: 0,
x: -300,
scale: 0.8
}
};
const pageTransition = {
type: "tween",
ease: "anticipate",
duration: 0.5
};
return (
<div style={{ position: 'relative', height: '400px', overflow: 'hidden' }}>
{/* ナビゲーション */}
<nav style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
{pages.map((page, index) => (
<button
key={page.id}
onClick={() => setCurrentPageIndex(index)}
style={{
padding: '8px 16px',
backgroundColor: index === currentPageIndex ? page.color : '#e5e7eb',
color: index === currentPageIndex ? 'white' : '#374151',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
{page.title}
</button>
))}
</nav>
{/* ページコンテンツ */}
<AnimatePresence mode="wait">
<motion.div
key={pages[currentPageIndex].id}
initial="initial"
animate="in"
exit="out"
variants={pageVariants}
transition={pageTransition}
style={{
position: 'absolute',
width: '100%',
height: '300px',
backgroundColor: pages[currentPageIndex].color,
borderRadius: '8px',
padding: '20px',
color: 'white'
}}
>
<h2>{pages[currentPageIndex].title}</h2>
<p>{pages[currentPageIndex].content}</p>
</motion.div>
</AnimatePresence>
</div>
);
};
まとめ
ReactでSVGアニメーションを実装する際は、目的に応じたツール選択が成功の鍵となります。
React-Springは物理法則に基づいた自然なアニメーションが得意で、ユーザーインターフェースの質感向上に最適です。Framer Motionは宣言的な記述で複雑なアニメーションを簡潔に実装でき、プロトタイピングから本格的な開発まで幅広く活用できます。
CSS-in-JSアプローチは既存のスタイルシステムとの統合がしやすく、useRefとuseEffectを使った手動制御は最大限の柔軟性を提供してくれます。
最も重要なのは、ユーザー体験を第一に考えることです。アニメーションは装飾ではなく、情報の伝達やユーザビリティの向上を目的とすべきです。パフォーマンスとアクセシビリティへの配慮を忘れずに、皆様のプロジェクトでもぜひ活用してみてください。
適切に実装されたSVGアニメーションは、ユーザーに「このアプリケーション、なんだか使いやすいな」という印象を与え、ブランド価値の向上にも寄与します。技術的な実装だけでなく、デザインの観点からもアニメーションを捉えることで、より魅力的なWebアプリケーションを作り上げることができるでしょう。
関連リンク
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来