不久前我为一个 SVG 时钟制作了动画:
注意:使用 dispose 以及我如何通过检查第二次更改将计算减少到仅在需要时。
Clock.razor
<div class="d-flex flex-column align-items-center">
<div class="time-sm">@timeZone.Id</div>
<svg width="@Size" height="@Size" viewBox="0 0 1000 1000">
<circle cx="@center" cy="@center" r="@radius" fill="@FaceColor" />
<ClockHand Angle="hourAngle" Color="@HourColor" Width="50" Length="0.9" />
<ClockHand Angle="minuteAngle" Color="@MinuteColor" Width="30" Length="0.95" />
<ClockHand Angle="secondAngle" Color="@SecondColor" Width="20" Length="1" />
</svg>
<div class="time-sm">@currentSecond.DateTime.ToShortTimeString()</div>
<div class="time-sm">@currentSecond.DateTime.ToString("dddd")</div>
</div>
Clock.razor.cs
public partial class Clock : ComponentBase, IDisposable
{
internal const int radius = 500;
internal const int size = 1000;
internal const int center = size / 2;
private double secondAngle = 0;
private double minuteAngle = 0;
private double hourAngle = 0;
private TimeZoneInfo timeZone;
private DateTimeOffset currentSecond;
private readonly System.Timers.Timer timer = new();
[Parameter]
public int Size { get; set; } = 50;
[Parameter]
public string FaceColor { get; set; } = "#f5f5f5";
[Parameter]
public string HourColor { get; set; } = "blue";
[Parameter]
public string MinuteColor { get; set; } = "green";
[Parameter]
public string SecondColor { get; set; } = "red";
[Parameter]
public string TimeZone { get; set; }
protected override void OnInitialized()
{
if (string.IsNullOrWhiteSpace(TimeZone) is true)
{
timeZone = TimeZoneInfo.Local;
}
else
{
timeZone = TimeZoneInfo.FindSystemTimeZoneById(TimeZone);
}
timer.Interval = 100;
timer.Elapsed += Timer_Elapsed;
UpdateClock();
timer.Start();
}
public static DateTime GmtToPacific(DateTime dateTime)
{
return TimeZoneInfo.ConvertTimeFromUtc(dateTime,
TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"));
}
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
UpdateClock();
InvokeAsync(StateHasChanged);
}
private void UpdateClock()
{
const double radiansPer60 = 360 / 60 * Math.PI / 180;
const double radiansPer12 = 360 / 12 * Math.PI / 180;
var currentTime = TimeZoneInfo.ConvertTime(DateTimeOffset.Now, timeZone);
var roundedSencond = new DateTimeOffset(currentTime.Year, currentTime.Month, currentTime.Day, currentTime.Hour, currentTime.Minute, currentTime.Second, default);
if (roundedSencond != currentSecond)
{
currentSecond = roundedSencond;
var seconds = currentTime.Second;
var minutes = currentTime.Minute;
var hours = currentTime.Hour % 12;
secondAngle = seconds * radiansPer60;
minuteAngle = minutes * radiansPer60 + secondAngle / 60;
hourAngle = hours * radiansPer12 + minuteAngle / 12;
}
}
public void Dispose()
{
if (timer is not null)
{
timer.Dispose();
}
}
}
ClockHand.razor
<line x1="500" y1="500" x2="@X" y2="@Y" style="stroke:@Color;stroke-width:@Width" />
ClockHand.razor.cs
public partial class ClockHand : ComponentBase
{
[Parameter]
public double Angle { get; set; }
[Parameter]
public double Length { get; set; } = 1;
[Parameter]
public string Color { get; set; } = "black";
[Parameter]
public int Width { get; set; }
double X => Math.Sin(Angle) * Clock.radius * Length + Clock.center;
double Y => Math.Cos(Angle) * -Clock.radius * Length + Clock.center;
}
Useage
<Clock TimeZone="Australia/Sydney" />
或
<Clock />