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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user