articles » current » blazor » wasm » timer-analog-clock

Blazor: Timer Example - Analog Clock

An example using the Timer class to create and set an analog clock

This code is compatible with .NET Core 3.1, .NET 5 and .NET 6.

Here is an example that uses the timer (System.Threading.Timer) to set an analog clock. It runs as client-side Blazor WASM code.

All measurements are based off of percentages so it will scale to practically any size.

For an example of the Timer class refreshing data from a WebApi on the screen, see this article.

Here is the clock specific CSS code that should be added to the Blazor WASM app.

.clock-container {
	background-color: midnightblue;
	display: flex;
	justify-content: center;
	align-items: center;
	width: 100%;
	min-height: 75vmin;
}

.clock {
	position: relative;
	overflow: hidden;
	background-color: inherit;
	height: 70vmin;
	width: 70vmin;
	border-radius: 50%;
	box-shadow: 0 -12px 12px rgba(255,255,255,.1),
				inset 0 -12px 12px rgba(255,255,255,.1),
				0 12px 12px rgba(0,0,0,.1),
				inset 0 12px 12px rgba(0,0,0,.1);
}
	.clock div {
		position: absolute;
		top: 0;
		left: 0;
		height: 100%;
		width: 100%;
		background-color: transparent;
	}
		.clock div div {
			left: 50%;
			width: 0;
		}
	.clock span {
		position: absolute;
		font-family: Arial;
		font-size: 5vmin;
		font-weight: bold;
		color: lime;
	}
	.clock .h12 {
		left: 50%;
		top: 3%;
		transform: translateX(-50%);
	}
	.clock .h12::before {
		content: "12";
	}
	.clock .h3 {
		left: 97%;
		top: 50%;
		transform: translate(-100%, -50%);
	}
	.clock .h3::before {
		content: "3";
	}
	.clock .h6 {
		left: 50%;
		top: 97%;
		transform: translate(-50%, -100%);
	}
	.clock .h6::before {
		content: "6";
	}
	.clock .h9 {
		left: 3%;
		top: 50%;
		transform: translateY(-50%);
	}
	.clock .h9::before {
		content: "9";
	}
	.clock .ctr {
		width: 3%;
		height: 3%;
		border-radius: 50%;
		left: 50%;
		top: 50%;
		transform: translate(-50%, -50%);
		background-color: wheat;
	}

.clock .hour div {
	top: 20%;
	height: 30%;
	border: 2px solid wheat;
	margin-left: -2px;
}

.clock .minute div {
	top: 10%;
	height: 40%;
	border: 2px solid wheat;
	margin-left: -2px;
}

.clock .second div {
	top: 5%;
	height: 65%;
	border: 1px solid red;
	margin-left: -1px;
}

Here is the Index.razor page that uses the Timer class. It needs to call StateHasChanged() because there is no user-fired event.

@page "/"

<div class="clock-container">
	<div class="clock">
		<span class="h12"></span>
		<span class="h3"></span>
		<span class="h6"></span>
		<span class="h9"></span>
		<div class="hour" style="transform:rotate(@(hr)deg);"><div></div></div>
		<div class="minute" style="transform:rotate(@(min)deg);"><div></div></div>
		<div class="second" style="transform:rotate(@(sec)deg);"><div></div></div>
		<span class="ctr"></span>
	</div>
</div>

@code {
	System.Threading.Timer timer;
	double hr, min, sec;

	// NOTE: this math can be simplified!!!
	private void SetClock(object stateInfo)
	{
		var time = DateTime.Now;
		hr = 360.0 * time.Hour / 12 + 30.0 * time.Minute / 60.0;
		min = 360.0 * time.Minute / 60 + 6.0 * time.Second / 60.0;
		sec = 360.0 * time.Second / 60 + 6.0 * time.Millisecond / 1000.0;
		StateHasChanged(); // MUST CALL StateHasChanged() BECAUSE THIS IS TRIGGERED BY A TIMER INSTEAD OF A USER EVENT
	}

	protected override async Task OnInitializedAsync()
	{
		await base.OnInitializedAsync();

		SetClock(null);

		timer = new System.Threading.Timer(SetClock, new System.Threading.AutoResetEvent(false), 10, 10); // 10 milliseconds
	}
}

Here is the same code for Index.razor that has been slightly modified to work with Blazor Server (SignalR, not WASM).

@page "/"

<div class="clock-container">
	<div class="clock">
		<span class="h12"></span>
		<span class="h3"></span>
		<span class="h6"></span>
		<span class="h9"></span>
		<div class="hour" style="transform:rotate(@(hr)deg);"><div></div></div>
		<div class="minute" style="transform:rotate(@(min)deg);"><div></div></div>
		<div class="second" style="transform:rotate(@(sec)deg);"><div></div></div>
		<span class="ctr"></span>
	</div>
</div>

@code {
	System.Threading.Timer timer;
	double hr, min, sec;

	// NOTE: this math can be simplified!!!
	private void SetClock(object stateInfo)
	{
		var time = DateTime.Now;
		hr = 360.0 * time.Hour / 12 + 30.0 * time.Minute / 60.0;
		min = 360.0 * time.Minute / 60 + 6.0 * time.Second / 60.0;
		sec = 360.0 * time.Second / 60 + 6.0 * time.Millisecond / 1000.0;
		InvokeAsync(() => StateHasChanged()); // MUST CALL StateHasChanged() BECAUSE THIS IS TRIGGERED BY A TIMER INSTEAD OF A USER EVENT
	}
	protected override void OnAfterRender(bool firstRender)
	{
		base.OnAfterRender(firstRender);

		SetClock(null);

		timer = new System.Threading.Timer(SetClock, new System.Threading.AutoResetEvent(false), 1000, 1000);

	}
}

Coding Video

https://youtu.be/9YXVZru1Khw


This site uses cookies. Cookies are simple text files stored on the user's computer. They are used for adding features and security to this site. Read the privacy policy.
CLOSE