Add typewriter component

This commit is contained in:
2024-03-06 21:37:45 +00:00
parent ac68998999
commit 1d6babdab5
7 changed files with 204 additions and 4 deletions

View File

@@ -1,7 +1,6 @@
<Router AppAssembly="@typeof(App).Assembly"> <Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData"> <Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found> </Found>
<NotFound> <NotFound>
<PageTitle>Not found</PageTitle> <PageTitle>Not found</PageTitle>

View File

@@ -0,0 +1,104 @@
@using System.Timers
@if (DisplayCursor)
{
<span>@currentText<span class="blinking-cursor">|</span></span>
}
else
{
<span>@currentText</span>
}
@code {
private const int typingDelayMilliseconds = 100;
private const int lineEndDelayMilliseconds = 1000;
private static List<Typewriter> instances = [];
private static Typewriter? lastTypingInstance = null;
private string currentText = "";
private bool isTyping = false;
private bool DisplayCursor => lastTypingInstance == this;
[Parameter]
public string Text { get; set; } = "";
protected override void OnInitialized()
{
Text = Text.Trim();
instances.Add(this);
StartTypingIfFirst();
}
private void StartTypingIfFirst()
{
if (instances.FirstOrDefault() == this && !isTyping)
{
StartTyping();
}
}
private void StartTyping()
{
isTyping = true;
lastTypingInstance = this;
var timer = new Timer(typingDelayMilliseconds);
var index = 0;
timer.Elapsed += (_, __) =>
{
if (index < Text.Length)
{
currentText += Text[index++];
InvokeAsync(StateHasChanged);
}
else
{
CompleteTyping(timer);
}
};
timer.Start();
}
private void CompleteTyping(Timer typingTimer)
{
typingTimer.Stop();
isTyping = false;
var delayTimer = new Timer(lineEndDelayMilliseconds);
delayTimer.Elapsed += (sender, e) =>
{
delayTimer.Stop();
delayTimer.Dispose();
UpdateCursorVisibility();
StartNextInstanceTyping();
InvokeAsync(StateHasChanged);
};
delayTimer.Start();
}
private void UpdateCursorVisibility()
{
lastTypingInstance = instances.LastOrDefault(i => !i.isTyping);
InvokeAsync(StateHasChanged);
}
private void StartNextInstanceTyping()
{
instances.Remove(this);
var nextInstance = instances.FirstOrDefault();
nextInstance?.StartTyping();
}
public static void Reset()
{
instances.Clear();
lastTypingInstance = null;
}
}

View File

@@ -1,3 +1,12 @@
@page "/" @page "/"
<PageTitle>Home</PageTitle> <PageTitle>Home - Beau Findlay</PageTitle>
<div>
<h1 class="text-3xl">
<Typewriter Text="Hi! I'm Beau."/>
</h1>
<p class="text-lg mt-3">
<Typewriter Text="Welcome to my portfolio site." />
</p>
</div>

View File

@@ -8,3 +8,4 @@
@using Microsoft.JSInterop @using Microsoft.JSInterop
@using BeauFindlay @using BeauFindlay
@using BeauFindlay.Layout @using BeauFindlay.Layout
@using BeauFindlay.Components.Typewriter

View File

@@ -1,3 +1,12 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@keyframes blink {
from, to { opacity: 1 }
50% { opacity: 0 }
}
.blinking-cursor {
animation: blink 1s step-end infinite;
}

View File

@@ -544,19 +544,80 @@ video {
--tw-backdrop-sepia: ; --tw-backdrop-sepia: ;
} }
.static {
position: static;
}
.mt-4 {
margin-top: 1rem;
}
.mt-3 {
margin-top: 0.75rem;
}
.flex {
display: flex;
}
.h-full {
height: 100%;
}
.min-h-screen { .min-h-screen {
min-height: 100vh; min-height: 100vh;
} }
.items-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.bg-black { .bg-black {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity)); background-color: rgb(0 0 0 / var(--tw-bg-opacity));
} }
.p-4 {
padding: 1rem;
}
.p-8 {
padding: 2rem;
}
.py-4 {
padding-top: 1rem;
padding-bottom: 1rem;
}
.py-8 {
padding-top: 2rem;
padding-bottom: 2rem;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.font-mono { .font-mono {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
} }
.text-lg {
font-size: 1.125rem;
line-height: 1.75rem;
}
.text-3xl {
font-size: 1.875rem;
line-height: 2.25rem;
}
.text-slate-50 { .text-slate-50 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(248 250 252 / var(--tw-text-opacity)); color: rgb(248 250 252 / var(--tw-text-opacity));
@@ -566,3 +627,17 @@ video {
-webkit-font-smoothing: auto; -webkit-font-smoothing: auto;
-moz-osx-font-smoothing: auto; -moz-osx-font-smoothing: auto;
} }
@keyframes blink {
from, to {
opacity: 1
}
50% {
opacity: 0
}
}
.blinking-cursor {
animation: blink 1s step-end infinite;
}

View File

@@ -12,7 +12,10 @@
</head> </head>
<body class="bg-black font-mono text-slate-50 min-h-screen subpixel-antialiased"> <body class="bg-black font-mono text-slate-50 min-h-screen subpixel-antialiased">
<div id="app"> <div id="app" class="p-8">
<div class="flex items-center justify-center text-lg">
<p>Loading beaufindlay.com<span class="blinking-cursor">|</span></p>
</div>
</div> </div>
<script src="_framework/blazor.webassembly.js"></script> <script src="_framework/blazor.webassembly.js"></script>
</body> </body>