Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 5 additions & 31 deletions scripts/generate-lqips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,15 @@ const IGNORE_DIRS = [
"public/assets/music/**",
];

interface RgbColor {
r: number;
g: number;
b: number;
}

type LqipMap = Record<string, string>;

function rgbToHex(color: RgbColor): string {
const hex = (n: number) => n.toString(16).padStart(2, "0");
return `#${hex(color.r)}${hex(color.g)}${hex(color.b)}`;
}

async function processImage(imagePath: string): Promise<string | null> {
try {
const { data, info } = await sharp(imagePath)
.resize(2, 2, { fit: "fill" })
.raw()
.toBuffer({ resolveWithObject: true });

const channels = info.channels;
const colors: RgbColor[] = [];

for (let i = 0; i < 4; i++) {
const offset = i * channels;
colors.push({
r: data[offset],
g: data[offset + 1],
b: data[offset + 2],
});
}

// 使用 corners[0], [1], [3] 生成 135deg 斜向渐变
const compact = `${rgbToHex(colors[0]).slice(1)}${rgbToHex(colors[1]).slice(1)}${rgbToHex(colors[3]).slice(1)}`;
return compact;
const buffer = await sharp(imagePath)
.resize(16, 16, { fit: "inside" })
.jpeg({ quality: 15, mozjpeg: true })
.toBuffer();
return `data:image/jpeg;base64,${buffer.toString("base64")}`;
} catch (error) {
console.error(`Error processing ${imagePath}:`, error);
return null;
Expand Down
20 changes: 18 additions & 2 deletions src/components/common/CoverImage.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
import { Picture } from "astro:assets";
import { getImage, Picture } from "astro:assets";
import * as path from "node:path";
import type { ImageMetadata } from "astro";
import { coverImageConfig } from "@/config";
Expand Down Expand Up @@ -136,6 +136,17 @@ const remoteReferrerPolicy =
!isLocal && shouldAddNoReferrer(isPublic ? url(src) : src)
? "no-referrer"
: undefined;

// Generate tiny image for local images if available
let tinyImgUrl = "";
if (isLocal && img) {
try {
const tinyImg = await getImage({ src: img, width: 20, quality: 20 });
tinyImgUrl = tinyImg.src;
} catch (e) {
console.error("Failed to generate tiny thumbnail:", e);
}
}
---

<div
Expand All @@ -145,7 +156,12 @@ const remoteReferrerPolicy =
data-api-urls={apiUrls.length > 0 ? JSON.stringify(apiUrls) : undefined}
>
<!-- LQIP 渐变占位 -->
<div class="lqip-placeholder absolute inset-0 pointer-events-none" style={lqipProps.style} aria-hidden="true"></div>
<div
class="lqip-placeholder absolute inset-0 pointer-events-none"
style={`${lqipProps.style};${tinyImgUrl ? `--tiny-src: url(${tinyImgUrl})` : ''}`}
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
data-tiny-src={tinyImgUrl ? "true" : undefined}
aria-hidden="true"
></div>

<!-- 加载动画 -->
{showLoading && (
Expand Down
20 changes: 18 additions & 2 deletions src/components/common/ImageWrapper.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
import { Image, Picture } from "astro:assets";
import { getImage, Image, Picture } from "astro:assets";
import * as path from "node:path";
import type { ImageMetadata } from "astro";
import type { ImageFormat, ResponsiveImageLayout } from "@/types/config";
Expand Down Expand Up @@ -103,9 +103,25 @@ const lqipProps = isLocal
: isPublic
? getLqipProps(src, basePath, true)
: getLqipProps(src);

// Generate tiny image for local images if available
let tinyImgUrl = "";
if (isLocal && img) {
try {
const tinyImg = await getImage({ src: img, width: 20, quality: 20 });
tinyImgUrl = tinyImg.src;
} catch (e) {
console.error("Failed to generate tiny thumbnail:", e);
}
}
---
<div id={id} class:list={[className, 'overflow-hidden relative']}>
<div class="lqip-placeholder absolute inset-0 pointer-events-none" style={lqipProps.style} aria-hidden="true"></div>
<div
class="lqip-placeholder absolute inset-0 pointer-events-none"
style={`${lqipProps.style};${tinyImgUrl ? `--tiny-src: url(${tinyImgUrl})` : ''}`}
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
data-tiny-src={tinyImgUrl ? "true" : undefined}
aria-hidden="true"
></div>
<div class="transition absolute inset-0 dark:bg-black/10 pointer-events-none"></div>
{isLocal && img && usePicture && (
<Picture
Expand Down
44 changes: 44 additions & 0 deletions src/components/controls/DisplaySettingsIntegrated.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
getStoredOverlayBlur,
getStoredOverlayCardOpacity,
getStoredOverlayOpacity,
getStoredProgressiveLoadingEnabled,
getStoredSakuraEnabled,
getStoredWallpaperMode,
getStoredWavesEnabled,
Expand All @@ -34,6 +35,7 @@ import {
setOverlayBlur,
setOverlayCardOpacity,
setOverlayOpacity,
setProgressiveLoadingEnabled,
setSakuraEnabled,
setWallpaperMode,
setWavesEnabled,
Expand Down Expand Up @@ -82,6 +84,7 @@ let bannerCarouselEnabled = $state(true);
const defaultBannerCarouselEnabled = getDefaultBannerCarouselEnabled();
let sakuraEnabled = $state(true);
const defaultSakuraEnabled = getDefaultSakuraEnabled();
let progressiveLoadingEnabled = $state(true);
let overlayOpacity = $state(getDefaultOverlayOpacity());
const defaultOverlayOpacity = getDefaultOverlayOpacity();
let overlayBlur = $state(getDefaultOverlayBlur());
Expand Down Expand Up @@ -309,6 +312,11 @@ function toggleSakuraEnabled() {
setSakuraEnabled(sakuraEnabled);
}

function toggleProgressiveLoading() {
progressiveLoadingEnabled = !progressiveLoadingEnabled;
setProgressiveLoadingEnabled(progressiveLoadingEnabled);
}

function switchWallpaperMode(newMode: WALLPAPER_MODE) {
wallpaperMode = newMode;
setWallpaperMode(newMode);
Expand Down Expand Up @@ -397,6 +405,9 @@ onMount(() => {
// 从localStorage读取樱花特效状态
sakuraEnabled = getStoredSakuraEnabled();

// 从localStorage读取渐进式图片加载状态
progressiveLoadingEnabled = getStoredProgressiveLoadingEnabled();

// 从localStorage读取全屏透明设置状态
overlayOpacity = getStoredOverlayOpacity();
overlayBlur = getStoredOverlayBlur();
Expand Down Expand Up @@ -746,6 +757,39 @@ $effect(() => {
</div>
{/if}

<!-- Image Settings Section -->
<div class="mt-2 mb-2">
<div class="flex gap-2 font-bold text-lg text-neutral-900 dark:text-neutral-100 transition relative ml-3 mb-2
before:w-1 before:h-4 before:rounded-md before:bg-(--primary)
before:absolute before:-left-3 before:top-1/2 before:-translate-y-1/2"
>
{i18n(I18nKey.imageSettings)}
<button aria-label="Reset to Default" class="btn-regular w-7 h-7 rounded-md active:scale-90"
class:opacity-0={progressiveLoadingEnabled === true} class:pointer-events-none={progressiveLoadingEnabled === true} onclick={() => { progressiveLoadingEnabled = true; setProgressiveLoadingEnabled(true); }}>
<div class="text-(--btn-content)">
<Icon icon="fa7-solid:arrow-rotate-left" class="text-[0.875rem]"></Icon>
</div>
</button>
</div>
<div class="space-y-1">
<button
class="w-full btn-regular rounded-md py-2 px-3 flex items-center gap-3 text-left active:scale-95 transition-all relative overflow-hidden"
class:bg-(--btn-regular-bg-hover)={progressiveLoadingEnabled}
onclick={toggleProgressiveLoading}
>
<Icon icon="material-symbols:image-outline" class="text-[1.25rem] shrink-0"></Icon>
<span class="text-sm flex-1">{i18n(I18nKey.progressiveLoading)}</span>
<div class="w-10 h-5 rounded-full transition-all duration-200 relative"
class:bg-(--primary)={progressiveLoadingEnabled}
class:bg-(--btn-regular-bg-active)={!progressiveLoadingEnabled}>
<div class="absolute top-0.5 w-4 h-4 bg-white rounded-full shadow transition-all duration-200"
class:left-0.5={!progressiveLoadingEnabled}
class:left-5={progressiveLoadingEnabled}></div>
</div>
</button>
</div>
</div>

<!-- Layout Switch Section -->
{#if allowLayoutSwitch}
<div class="mt-2 mb-2">
Expand Down
Loading