Added progressive loading of background images with crossfade and blur.

This commit is contained in:
Jeremy Karst 2026-01-06 23:33:22 -05:00
parent 4a491d2d72
commit 9fd3918a5d
25 changed files with 132 additions and 6 deletions

View file

@ -45,6 +45,16 @@
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
z-index: 0; z-index: 0;
filter: blur(10px) saturate(0.65);
}
.background-full {
opacity: 0;
transition: opacity 2s ease, filter 2s ease;
}
.background-full.loaded {
opacity: 1;
filter: blur(4px) saturate(0.65); filter: blur(4px) saturate(0.65);
} }

View file

@ -2,7 +2,7 @@
description: "KarstTech LLC is a company which provides Technical and Scientific Consulting and Data Science services." description: "KarstTech LLC is a company which provides Technical and Scientific Consulting and Data Science services."
--- ---
{{< figure src="img/karsttech_logo_vert.png" >}} ![KarstTech Logo](img/karsttech_logo_vert.png)
{{< lead >}} {{< lead >}}

View file

@ -13,7 +13,7 @@ showFeature = false
<!--more--> <!--more-->
<div style="width:100%; max-width:640px; aspect-ratio:16/9; position:relative; overflow:hidden;"> <div style="width:100%; max-width:640px; aspect-ratio:16/9; position:relative; overflow:hidden;">
<iframe src="/matrix-synapse-docker-compose/matrix-synapse-thumbnail.html" style="border:none; width:200%; height:200%; transform:scale(0.5); transform-origin:0 0;"></iframe> <iframe src="/articles-special-content/matrix-synapse-docker-compose/matrix-synapse-thumbnail.html" style="border:none; width:200%; height:200%; transform:scale(0.5); transform-origin:0 0;"></iframe>
</div> </div>
Synapse is a great implementation of the [matrix] federated chat platform, but the [official documentation](https://matrix-org.github.io/synapse/latest/setup/installation.html) expects you to run several manual initialization steps before your server is ready to use. This gets tedious when you're just want to use a docker compose file to plug in your info and get going, especially if you are using tools like Portainer to administrate your containers. Synapse is a great implementation of the [matrix] federated chat platform, but the [official documentation](https://matrix-org.github.io/synapse/latest/setup/installation.html) expects you to run several manual initialization steps before your server is ready to use. This gets tedious when you're just want to use a docker compose file to plug in your info and get going, especially if you are using tools like Portainer to administrate your containers.

View file

@ -0,0 +1,32 @@
{{ $url := urls.Parse .Destination }}
{{ $altText := .Text }}
{{ $caption := .Title }}
{{ $class := "mx-auto my-0 rounded-md" }}
{{ $file := $url.Path }}
{{ $img := .Page.Resources.GetMatch $file }}
{{- if and (not $img) .Page.File }}
{{ $path := path.Join .Page.File.Dir $file }}
{{ $img = resources.Get $path }}
{{ end -}}
{{/* https://github.com/gohugoio/hugo/pull/10666 */}}
{{- $params := $url.Query -}}
{{- $x2Param := $params.Get "2x" -}}
{{- $x2 := false -}}
{{- if eq $x2Param "true" -}}
{{- $x2 = true -}}
{{- end -}}
<figure>
{{- with $img -}}
{{ $lazy := $.Page.Site.Params.enableImageLazyLoading | default true }}
{{ $webp := $.Page.Site.Params.enableImageWebp | default true }}
{{ $lqip := true }}
{{ partial "picture.html" (dict "img" . "alt" $altText "class" $class "x2" $x2 "lazy" $lazy "webp" $webp "lqip" $lqip) }}
{{- else -}}
<img src="{{ .Destination | safeURL }}" alt="{{ $altText }}" class="{{ $class }}" />
{{- end -}}
{{ with $caption }}<figcaption class="text-center">{{ . | markdownify }}</figcaption>{{ end }}
</figure>

View file

@ -1,15 +1,35 @@
{{ $images := slice }} {{ $images := slice }}
{{ range readDir "static/backgrounds" }} {{ range readDir "static/backgrounds" }}
{{ if in .Name ".webp" }} {{ if or (in .Name ".webp") (in .Name ".jpg") }}
{{ $images = $images | append .Name }} {{ $images = $images | append .Name }}
{{ end }} {{ end }}
{{ end }} {{ end }}
<div class="background-container"> <div class="background-container">
<img id="background-image" alt="Background" class="background-image"> <img id="background-thumb" alt="Background" class="background-image">
<img id="background-full" alt="Background" class="background-image background-full" loading="lazy">
<script> <script>
const images = {{ $images }}; const images = {{ $images }};
const randomImage = images[Math.floor(Math.random() * images.length)]; const randomImage = images[Math.floor(Math.random() * images.length)];
document.getElementById('background-image').src = `/backgrounds/${randomImage}`; const thumbImg = document.getElementById('background-thumb');
const fullImg = document.getElementById('background-full');
const thumbPath = `/backgrounds/thumbs/${randomImage}`;
const fullPath = `/backgrounds/${randomImage}`;
thumbImg.src = thumbPath;
setTimeout(() => {
const preloadImg = new Image();
preloadImg.onload = () => {
fullImg.src = fullPath;
fullImg.classList.add('loaded');
setTimeout(() => thumbImg.remove(), 2000);
};
preloadImg.onerror = () => {
fullImg.src = fullPath;
fullImg.classList.add('loaded');
};
preloadImg.src = fullPath;
}, 800);
</script> </script>
</div> </div>

View file

@ -1,5 +1,29 @@
<div class="background-container"> <div class="background-container">
<img src="/backgrounds/Apophysis_071008-64.webp" alt="Background" class="background-image"> <img id="background-thumb" alt="Background" class="background-image">
<img id="background-full" alt="Background" class="background-image background-full" loading="lazy">
<script>
const thumbImg = document.getElementById('background-thumb');
const fullImg = document.getElementById('background-full');
const imageName = 'Apophysis_071008-64.webp';
const thumbPath = `/backgrounds/thumbs/${imageName}`;
const fullPath = `/backgrounds/${imageName}`;
thumbImg.src = thumbPath;
setTimeout(() => {
const preloadImg = new Image();
preloadImg.onload = () => {
fullImg.src = fullPath;
fullImg.classList.add('loaded');
setTimeout(() => thumbImg.remove(), 2000);
};
preloadImg.onerror = () => {
fullImg.src = fullPath;
fullImg.classList.add('loaded');
};
preloadImg.src = fullPath;
}, 800);
</script>
</div> </div>
<article class="prose max-w-full dark:prose-invert glass"> <article class="prose max-w-full dark:prose-invert glass">
{{ with .Title }} {{ with .Title }}

View file

@ -0,0 +1,40 @@
{{- if .Site.Params.header.logo }}
{{- $logo := resources.Get .Site.Params.header.logo }}
{{- $logo_dark := resources.Get .Site.Params.header.logoDark }}
{{- if $logo }}
{{- $logoResized := $logo.Resize (printf "%dx" (div $logo.Width 2)) }}
{{- $logoDarkResized := "" }}
{{- if $logo_dark }}
{{- $logoDarkResized = $logo_dark.Resize (printf "%dx" (div $logo_dark.Width 2)) }}
{{- end }}
<a href="{{ "" | relLangURL }}" class="mr-2">
<!-- prettier-ignore-attribute -->
<img
src="{{ $logoResized.RelPermalink }}"
width="{{ $logoResized.Width }}"
height="{{ $logoResized.Height }}"
class="max-h-[10rem] max-w-[10rem] object-scale-down object-left
{{ if $logo_dark }}hidden dark:flex{{ end }}"
alt="{{ .Site.Title }}"
/>
{{- if $logo_dark }}
<img
src="{{ $logoDarkResized.RelPermalink }}"
width="{{ $logoDarkResized.Width }}"
height="{{ $logoDarkResized.Height }}"
class="max-h-[10rem] max-w-[10rem] object-scale-down object-left dark:hidden"
alt="{{ .Site.Title }}"
/>
{{- end }}
</a>
{{- end }}
{{- end }}
{{- if .Site.Params.header.showTitle | default true }}
<a
class="decoration-primary-500 hover:underline hover:decoration-2 hover:underline-offset-2"
rel="me"
href="{{ "" | relLangURL }}"
>{{ .Site.Title | markdownify | emojify }}</a
>
{{- end }}

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB