基礎元件介面都能處理。細節需要逐步展開處理。
https://v0.build/4Mt2YNQ
prompt: use react, tailwindcss, css3, canvas, programming. iterate on the design to make it match the image as closely as possible.
traning tips: In this updated version, I've made the following changes to match the UI more closely.
import React, { useEffect, useRef, useState } from 'react';
export default function SleepTracker() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [sleepTime, setSleepTime] = useState<Date>(new Date(2023, 0, 1, 0, 0));
const [wakeTime, setWakeTime] = useState<Date>(new Date(2023, 0, 1, 7, 0));
const [isDragging, setIsDragging] = useState<'sleep' | 'wake' | null>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const drawSleepTracker = () => {
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Set background
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw sleep time
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 44px Arial';
ctx.textAlign = 'left';
ctx.fillText(formatTime(sleepTime), 20, 70);
// Draw wake time
ctx.textAlign = 'right';
ctx.fillText(formatTime(wakeTime), canvas.width - 20, 70);
// Draw sleep icon (moon)
drawMoonIcon(ctx, 30, 110);
// Draw wake icon (bell)
drawBellIcon(ctx, canvas.width - 30, 110);
// Draw "就寝時刻" text
ctx.fillStyle = '#ffffff';
ctx.font = '16px Arial';
ctx.textAlign = 'left';
ctx.fillText('就寝時刻', 60, 115);
// Draw "起床時刻" text
ctx.textAlign = 'right';
ctx.fillText('起床時刻', canvas.width - 60, 115);
// Draw clock
const centerX = canvas.width / 2;
const centerY = canvas.height / 2 + 60;
const radius = 140;
// Draw clock circle
ctx.strokeStyle = '#333333';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
ctx.stroke();
// Draw sleep arc
const startAngle = (sleepTime.getHours() * 60 + sleepTime.getMinutes()) * Math.PI / 720 - Math.PI / 2;
const endAngle = (wakeTime.getHours() * 60 + wakeTime.getMinutes()) * Math.PI / 720 - Math.PI / 2;
const gradient = ctx.createLinearGradient(centerX - radius, centerY, centerX + radius, centerY);
gradient.addColorStop(0, '#ff8c00');
gradient.addColorStop(0.6, '#ffd700');
gradient.addColorStop(1, '#ffff00');
ctx.strokeStyle = gradient;
ctx.lineWidth = 14;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, startAngle, endAngle);
ctx.stroke();
// Draw clock numbers and tick marks
ctx.fillStyle = '#ffffff';
ctx.font = '20px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
for (let i = 0; i < 60; i++) {
const angle = i * Math.PI / 30;
const innerRadius = i % 5 === 0 ? radius - 15 : radius - 5;
const x1 = centerX + Math.cos(angle) * radius;
const y1 = centerY + Math.sin(angle) * radius;
const x2 = centerX + Math.cos(angle) * innerRadius;
const y2 = centerY + Math.sin(angle) * innerRadius;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.strokeStyle = '#666666';
ctx.lineWidth = 1;
ctx.stroke();
if (i % 15 === 0) {
const x = centerX + Math.cos(angle) * (radius - 30);
const y = centerY + Math.sin(angle) * (radius - 30);
ctx.fillText((i / 5 || 12).toString(), x, y);
}
}
// Draw moon icon at sleep time
const sleepIconX = centerX + Math.cos(startAngle) * (radius + 15);
const sleepIconY = centerY + Math.sin(startAngle) * (radius + 15);
drawMoonIcon(ctx, sleepIconX, sleepIconY, 12);
// Draw bell icon at wake time
const wakeIconX = centerX + Math.cos(endAngle) * (radius + 15);
const wakeIconY = centerY + Math.sin(endAngle) * (radius + 15);
drawBellIcon(ctx, wakeIconX, wakeIconY, 12);
// Draw sleep duration
const duration = calculateDuration(sleepTime, wakeTime);
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
ctx.fillText(`${duration.toFixed(1)}時間`, centerX, centerY);
// Draw draggable handles
drawHandle(ctx, centerX, centerY, radius, sleepTime, '#ffa500');
drawHandle(ctx, centerX, centerY, radius, wakeTime, '#ffd700');
};
drawSleepTracker();
}, [sleepTime, wakeTime]);
const formatTime = (date: Date) => {
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false });
};
const calculateDuration = (start: Date, end: Date) => {
let duration = (end.getTime() - start.getTime()) / (1000 * 60 * 60);
if (duration < 0) duration += 24;
return duration;
};
const drawMoonIcon = (ctx: CanvasRenderingContext2D, x: number, y: number, size: number = 18) => {
ctx.fillStyle = '#ffa500';
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(x + size / 3, y, size * 0.8, 0, Math.PI * 2);
ctx.fill();
};
const drawBellIcon = (ctx: CanvasRenderingContext2D, x: number, y: number, size: number = 18) => {
const scale = size / 18;
ctx.fillStyle = '#ffd700';
ctx.beginPath();
ctx.moveTo(x - 12 * scale, y + 10 * scale);
ctx.lineTo(x + 12 * scale, y + 10 * scale);
ctx.lineTo(x + 15 * scale, y - 3 * scale);
ctx.lineTo(x + 10 * scale, y - 15 * scale);
ctx.lineTo(x - 10 * scale, y - 15 * scale);
ctx.lineTo(x - 15 * scale, y - 3 * scale);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(x, y + 12 * scale, 4 * scale, 0, Math.PI * 2);
ctx.fill();
};
const drawHandle = (ctx: CanvasRenderingContext2D, centerX: number, centerY: number, radius: number, time: Date, color: string) => {
const angle = (time.getHours() * 60 + time.getMinutes()) * Math.PI / 720 - Math.PI / 2;
const x = centerX + Math.cos(angle) * radius;
const y = centerY + Math.sin(angle) * radius;
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, 12, 0, Math.PI * 2);
ctx.fill();
};
const handleMouseDown = (event: React.MouseEvent<HTMLCanvasElement>) => {
const canvas = canvasRef.current;
if (!canvas) return;
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2 + 60;
const radius = 140;
const sleepAngle = (sleepTime.getHours() * 60 + sleepTime.getMinutes()) * Math.PI / 720 - Math.PI / 2;
const wakeAngle = (wakeTime.getHours() * 60 + wakeTime.getMinutes()) * Math.PI / 720 - Math.PI / 2;
const sleepX = centerX + Math.cos(sleepAngle) * radius;
const sleepY = centerY + Math.sin(sleepAngle) * radius;
const wakeX = centerX + Math.cos(wakeAngle) * radius;
const wakeY = centerY + Math.sin(wakeAngle) * radius;
const sleepDist = Math.sqrt((x - sleepX) ** 2 + (y - sleepY) ** 2);
const wakeDist = Math.sqrt((x - wakeX) ** 2 + (y - wakeY) ** 2);
if (sleepDist < 15) {
setIsDragging('sleep');
} else if (wakeDist < 15) {
setIsDragging('wake');
}
};
const handleMouseMove = (event: React.MouseEvent<HTMLCanvasElement>) => {
if (!isDragging) return;
const canvas = canvasRef.current;
if (!canvas) return;
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2 + 60;
const angle = Math.atan2(y - centerY, x - centerX);
const hours = Math.floor(((angle + Math.PI / 2 + Math.PI * 2) % (Math.PI * 2)) * 12 / Math.PI);
const minutes = Math.floor((((angle + Math.PI / 2 + Math.PI * 2) % (Math.PI * 2)) * 12 / Math.PI - hours) * 60);
const newTime = new Date(2023, 0, 1, hours, minutes);
if (isDragging === 'sleep') {
setSleepTime(newTime);
} else {
setWakeTime(newTime);
}
};
const handleMouseUp = () => {
setIsDragging(null);
};
return (
<div className="flex justify-center items-center h-screen bg-black">
<canvas
ref={canvasRef}
width={350}
height={520}
className="border border-gray-700 rounded-lg cursor-pointer"
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
/>
</div>
);
}
點讚的用戶