feat(docs): add NavGrid/NavCard components and restructure stage pages

- Add NavGrid.vue and NavCard.vue components for better navigation layout
- Restructure stage-0 index pages across languages into intro.md with new navigation components
- Remove old stage-0 index.md files and update stage-3 pages similarly
- Add new dependencies 'claude' and 'codex' to package.json
- Improve code formatting in multiple Vue components for better readability
- Update documentation content and structure for better user experience
This commit is contained in:
sanbuphy
2026-02-01 23:42:12 +08:00
parent a9a5c5c8a7
commit ad95658a11
171 changed files with 16366 additions and 7946 deletions
@@ -38,17 +38,18 @@
<div class="parameters">
<div class="param-row">
<label>Speed / 速度: {{ speed }}x</label>
<input type="range" v-model.number="speed" min="0.1" max="3" step="0.1" />
<input
type="range"
v-model.number="speed"
min="0.1"
max="3"
step="0.1"
/>
</div>
<div class="param-row">
<label>Object Count / 对象数量: {{ objectCount }}</label>
<input
type="range"
v-model.number="objectCount"
min="1"
max="10"
/>
<input type="range" v-model.number="objectCount" min="1" max="10" />
</div>
</div>
@@ -99,7 +100,8 @@
<p>
<span class="icon">💡</span>
<strong>提示</strong>
动画的本质是快速连续绘制静态画面Canvas 每秒可以绘制 60 60FPS形成流畅的动画效果
动画的本质是快速连续绘制静态画面Canvas 每秒可以绘制 60
60FPS形成流畅的动画效果
</p>
</div>
</div>
@@ -259,7 +261,13 @@ const drawBouncingBall = (ctx) => {
// 高光效果
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'
ctx.beginPath()
ctx.arc(ball.x - ball.radius * 0.3, ball.y - ball.radius * 0.3, ball.radius * 0.4, 0, Math.PI * 2)
ctx.arc(
ball.x - ball.radius * 0.3,
ball.y - ball.radius * 0.3,
ball.radius * 0.4,
0,
Math.PI * 2
)
ctx.fill()
})
}
@@ -487,7 +495,7 @@ onUnmounted(() => {
color: #555;
}
.param-row input[type="range"] {
.param-row input[type='range'] {
width: 100%;
}
@@ -41,22 +41,12 @@
<div class="param-row">
<label>Stroke Width / 描边宽度: {{ strokeWidth }}px</label>
<input
type="range"
v-model.number="strokeWidth"
min="1"
max="20"
/>
<input type="range" v-model.number="strokeWidth" min="1" max="20" />
</div>
<div class="param-row" v-if="currentShape === 'rect'">
<label>Size / 大小: {{ rectSize }}px</label>
<input
type="range"
v-model.number="rectSize"
min="20"
max="200"
/>
<input type="range" v-model.number="rectSize" min="20" max="200" />
</div>
<div class="param-row" v-if="currentShape === 'circle'">
@@ -71,12 +61,7 @@
<div class="param-row" v-if="currentShape === 'line'">
<label>Line Length / 线条长度: {{ lineLength }}px</label>
<input
type="range"
v-model.number="lineLength"
min="50"
max="300"
/>
<input type="range" v-model.number="lineLength" min="50" max="300" />
</div>
</div>
@@ -104,7 +89,8 @@
<p>
<span class="icon">💡</span>
<strong>提示</strong>
Canvas 是一个位图画布所有绘制都是像素操作绘制后无法修改已有内容只能覆盖或清除重绘
Canvas
是一个位图画布所有绘制都是像素操作绘制后无法修改已有内容只能覆盖或清除重绘
</p>
</div>
</div>
@@ -312,11 +298,11 @@ onMounted(() => {
color: #555;
}
.param-row input[type="range"] {
.param-row input[type='range'] {
width: 100%;
}
.param-row input[type="color"] {
.param-row input[type='color'] {
width: 100%;
height: 36px;
border: 1px solid #ddd;
@@ -45,7 +45,9 @@
</div>
<div class="info-item" v-if="selectedPoint">
<span class="label">Selected Point:</span>
<span class="value">({{ selectedPoint.x }}, {{ selectedPoint.y }})</span>
<span class="value"
>({{ selectedPoint.x }}, {{ selectedPoint.y }})</span
>
</div>
</div>
</div>
@@ -65,18 +67,14 @@
<div class="explanation">
<h4>Canvas Coordinate System / Canvas 坐标系统</h4>
<ul>
<li>
<strong>Origin / 原点</strong>在左上角坐标为 (0, 0)
</li>
<li><strong>Origin / 原点</strong>在左上角坐标为 (0, 0)</li>
<li>
<strong>X Axis / X </strong>向右为正方向 0 canvas.width
</li>
<li>
<strong>Y Axis / Y </strong>向下为正方向 0 canvas.height
</li>
<li>
<strong>Unit / 单位</strong>像素 (px) CSS 像素 1:1 对应
</li>
<li><strong>Unit / 单位</strong>像素 (px) CSS 像素 1:1 对应</li>
</ul>
</div>
@@ -112,7 +110,8 @@ ctx.fill()</code></pre>
<p>
<span class="icon">💡</span>
<strong>提示</strong>
Canvas Y 轴方向与传统数学坐标系相反向下为正这在处理图形定位时需要特别注意
Canvas Y
轴方向与传统数学坐标系相反向下为正这在处理图形定位时需要特别注意
</p>
</div>
</div>
@@ -187,7 +186,8 @@ const drawAxis = (ctx) => {
const drawPoints = (ctx) => {
points.forEach((point, index) => {
// 绘制点
ctx.fillStyle = index === 0 ? '#e74c3c' : index === 1 ? '#3498db' : '#2ecc71'
ctx.fillStyle =
index === 0 ? '#e74c3c' : index === 1 ? '#3498db' : '#2ecc71'
ctx.beginPath()
ctx.arc(point.x, point.y, 8, 0, Math.PI * 2)
ctx.fill()
@@ -196,7 +196,11 @@ const drawPoints = (ctx) => {
if (showCoordinates.value) {
ctx.fillStyle = '#2c3e50'
ctx.font = '12px Arial'
ctx.fillText(`(${Math.round(point.x)}, ${Math.round(point.y)})`, point.x + 12, point.y - 12)
ctx.fillText(
`(${Math.round(point.x)}, ${Math.round(point.y)})`,
point.x + 12,
point.y - 12
)
}
})
}
@@ -293,7 +297,7 @@ onMounted(() => {
font-size: 14px;
}
.toggle-option input[type="checkbox"] {
.toggle-option input[type='checkbox'] {
width: 18px;
height: 18px;
cursor: pointer;
@@ -32,7 +32,8 @@
<h4>Instructions / 操作说明</h4>
<ul>
<li v-if="currentMode === 'click'">
<strong>Click Mode</strong>点击画布创建圆形按住 Shift 可创建不同颜色
<strong>Click Mode</strong>点击画布创建圆形按住 Shift
可创建不同颜色
</li>
<li v-if="currentMode === 'drag'">
<strong>Drag Mode</strong>拖拽圆形移动位置拖拽时会改变颜色
@@ -41,7 +42,8 @@
<strong>Hover Mode</strong>鼠标悬停在圆形上会高亮显示并显示坐标
</li>
<li v-if="currentMode === 'keyboard'">
<strong>Keyboard Mode</strong>使用方向键移动选中的圆形Delete 键删除
<strong>Keyboard Mode</strong>使用方向键移动选中的圆形Delete
键删除
</li>
</ul>
</div>
@@ -129,7 +131,14 @@ const modes = [
{ value: 'keyboard', label: 'Keyboard / 键盘' }
]
const colors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c']
const colors = [
'#e74c3c',
'#3498db',
'#2ecc71',
'#f39c12',
'#9b59b6',
'#1abc9c'
]
const currentCode = computed(() => {
const templates = {
@@ -289,7 +298,13 @@ const draw = () => {
// 高光
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'
ctx.beginPath()
ctx.arc(circle.x - circle.radius * 0.3, circle.y - circle.radius * 0.3, circle.radius * 0.4, 0, Math.PI * 2)
ctx.arc(
circle.x - circle.radius * 0.3,
circle.y - circle.radius * 0.3,
circle.radius * 0.4,
0,
Math.PI * 2
)
ctx.fill()
// 选中状态
@@ -311,7 +326,11 @@ const draw = () => {
// 显示坐标
ctx.fillStyle = '#2c3e50'
ctx.font = '12px Arial'
ctx.fillText(`(${Math.round(circle.x)}, ${Math.round(circle.y)})`, circle.x + circle.radius + 10, circle.y)
ctx.fillText(
`(${Math.round(circle.x)}, ${Math.round(circle.y)})`,
circle.x + circle.radius + 10,
circle.y
)
}
})
}
@@ -320,7 +339,9 @@ const handleClick = (e) => {
if (currentMode.value !== 'click') return
const { x, y } = getMousePos(e)
const color = e.shiftKey ? colors[Math.floor(Math.random() * colors.length)] : '#3498db'
const color = e.shiftKey
? colors[Math.floor(Math.random() * colors.length)]
: '#3498db'
circles.value.push({
x,
@@ -336,7 +357,11 @@ const handleClick = (e) => {
const handleMouseMove = (e) => {
const { x, y } = getMousePos(e)
if (currentMode.value === 'drag' && isDragging.value && selectedCircle.value) {
if (
currentMode.value === 'drag' &&
isDragging.value &&
selectedCircle.value
) {
selectedCircle.value.x = x
selectedCircle.value.y = y
draw()
@@ -355,7 +380,10 @@ const handleMouseMove = (e) => {
if (found !== hoveredCircle.value) {
hoveredCircle.value = found
if (found) {
addLog(`Hovering circle at (${Math.round(found.x)}, ${Math.round(found.y)})`, 'info')
addLog(
`Hovering circle at (${Math.round(found.x)}, ${Math.round(found.y)})`,
'info'
)
}
}
draw()
@@ -372,14 +400,20 @@ const handleMouseDown = (e) => {
if (dist < circle.radius) {
isDragging.value = true
selectedCircle.value = circle
addLog(`Started dragging circle at (${Math.round(x)}, ${Math.round(y)})`, 'info')
addLog(
`Started dragging circle at (${Math.round(x)}, ${Math.round(y)})`,
'info'
)
}
})
}
const handleMouseUp = () => {
if (isDragging.value) {
addLog(`Dropped circle at (${Math.round(selectedCircle.value.x)}, ${Math.round(selectedCircle.value.y)})`, 'success')
addLog(
`Dropped circle at (${Math.round(selectedCircle.value.x)}, ${Math.round(selectedCircle.value.y)})`,
'success'
)
}
isDragging.value = false
selectedCircle.value = null
@@ -41,17 +41,18 @@
<div class="param-row">
<label>Particle Size / 粒子大小: {{ particleSize }}</label>
<input
type="range"
v-model.number="particleSize"
min="1"
max="10"
/>
<input type="range" v-model.number="particleSize" min="1" max="10" />
</div>
<div class="param-row">
<label>Speed / 速度: {{ speed }}</label>
<input type="range" v-model.number="speed" min="0.5" max="3" step="0.1" />
<input
type="range"
v-model.number="speed"
min="0.5"
max="3"
step="0.1"
/>
</div>
<div class="param-row">
@@ -205,7 +206,16 @@ function animate() {
}`
})
const colors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c', '#e91e63', '#00bcd4']
const colors = [
'#e74c3c',
'#3498db',
'#2ecc71',
'#f39c12',
'#9b59b6',
'#1abc9c',
'#e91e63',
'#00bcd4'
]
class Particle {
constructor(x, y, effect) {
@@ -284,7 +294,10 @@ const draw = () => {
const ctx = canvas.getContext('2d')
// 清除画布(使用半透明背景产生拖尾效果)
ctx.fillStyle = currentEffect.value === 'trail' ? 'rgba(250, 250, 250, 0.2)' : 'rgba(250, 250, 250, 1)'
ctx.fillStyle =
currentEffect.value === 'trail'
? 'rgba(250, 250, 250, 0.2)'
: 'rgba(250, 250, 250, 1)'
ctx.fillRect(0, 0, canvas.width, canvas.height)
// 更新和绘制粒子
@@ -432,7 +445,7 @@ onUnmounted(() => {
color: #555;
}
.param-row input[type="range"] {
.param-row input[type='range'] {
width: 100%;
}
@@ -65,7 +65,14 @@
<div class="stats">
<div class="stat-item">
<span class="label">FPS:</span>
<span class="value" :class="{ good: fps >= 55, warning: fps >= 30 && fps < 55, bad: fps < 30 }">
<span
class="value"
:class="{
good: fps >= 55,
warning: fps >= 30 && fps < 55,
bad: fps < 30
}"
>
{{ fps }}
</span>
</div>
@@ -116,17 +123,23 @@
<tr v-if="useDirtyRect">
<td>Dirty Rect / 脏矩形</td>
<td>{{ fps }}</td>
<td>{{ (((fps - baselineFps) / baselineFps) * 100).toFixed(1) }}%</td>
<td>
{{ (((fps - baselineFps) / baselineFps) * 100).toFixed(1) }}%
</td>
</tr>
<tr v-if="useOffscreenCanvas">
<td>Offscreen Canvas / 离屏画布</td>
<td>{{ fps }}</td>
<td>{{ (((fps - baselineFps) / baselineFps) * 100).toFixed(1) }}%</td>
<td>
{{ (((fps - baselineFps) / baselineFps) * 100).toFixed(1) }}%
</td>
</tr>
<tr v-if="useBatching">
<td>Batch Rendering / 批量渲染</td>
<td>{{ fps }}</td>
<td>{{ (((fps - baselineFps) / baselineFps) * 100).toFixed(1) }}%</td>
<td>
{{ (((fps - baselineFps) / baselineFps) * 100).toFixed(1) }}%
</td>
</tr>
</tbody>
</table>
@@ -335,7 +348,12 @@ const drawRedrawTest = (ctx) => {
// 只重绘移动的对象
objects.forEach((obj) => {
if (obj.moved) {
ctx.clearRect(obj.oldX - obj.size - 1, obj.oldY - obj.size - 1, obj.size * 2 + 2, obj.size * 2 + 2)
ctx.clearRect(
obj.oldX - obj.size - 1,
obj.oldY - obj.size - 1,
obj.size * 2 + 2,
obj.size * 2 + 2
)
ctx.fillStyle = obj.color
ctx.beginPath()
ctx.arc(obj.x, obj.y, obj.size, 0, Math.PI * 2)
@@ -471,7 +489,12 @@ const animate = (timestamp) => {
fpsTime += deltaTime
if (fpsTime >= 1000) {
fps.value = Math.round((frameCount * 1000) / fpsTime)
if (!showComparison.value && !useDirtyRect.value && !useOffscreenCanvas.value && !useBatching.value) {
if (
!showComparison.value &&
!useDirtyRect.value &&
!useOffscreenCanvas.value &&
!useBatching.value
) {
baselineFps.value = fps.value
}
frameCount = 0
@@ -594,7 +617,7 @@ onUnmounted(() => {
color: #555;
}
.param-row input[type="range"] {
.param-row input[type='range'] {
width: 100%;
}
@@ -627,7 +650,7 @@ onUnmounted(() => {
border: 1px solid #ddd;
}
.toggle-option input[type="checkbox"] {
.toggle-option input[type='checkbox'] {
width: 18px;
height: 18px;
cursor: pointer;