Add typewriter component
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
104
BeauFindlay/BeauFindlay/Components/Typewriter/Typewriter.razor
Normal file
104
BeauFindlay/BeauFindlay/Components/Typewriter/Typewriter.razor
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -8,3 +8,4 @@
|
|||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using BeauFindlay
|
@using BeauFindlay
|
||||||
@using BeauFindlay.Layout
|
@using BeauFindlay.Layout
|
||||||
|
@using BeauFindlay.Components.Typewriter
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
75
BeauFindlay/BeauFindlay/wwwroot/css/app.min.css
vendored
75
BeauFindlay/BeauFindlay/wwwroot/css/app.min.css
vendored
@@ -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;
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user