Added parallax to background image

This commit is contained in:
DrMint 2024-05-25 17:08:30 +02:00
parent 2e0b586569
commit 967ccf3184
5 changed files with 305 additions and 34 deletions

131
README.md
View File

@ -1,5 +1,136 @@
# Accord's Library # Accord's Library
## Tech overview
- Client-side framework: None
- Web framework / server: [Astro](https://astro.build)
- Content management system: [Payload](https://payloadcms.com)
- Database: MongoDB
## Core
Accord's Library v3.0 (shorten to AL3.0) follows the Metamodernist Web model described by Frédéric Bonnet in his article [From Classicism to Metamodernism — A Short History of Web Development](https://dev.to/fredericbonnet/the-third-age-of-web-development-kgj#the-metamodernist-period).
- Embrace web standards instead of reinventing the wheel
- [Progressive enhancement](https://en.wikipedia.org/wiki/Progressive_enhancement): SSG or SSR for noscript clients. SPA-like enhancements such as partial page updates and view transitions for clients with JS support.
- Mimimal dependencies. Dependencies can be self-hosted or loaded directly from CDNs instead of being bundled up.
- Accessible, fast, lightweight, substainable
- Complexity is moved away from client devices
## Focal points
- Progressive enhancement / Graceful degradation
- Fully functional without JS
- Only use JS for non-essential features
- When JS is not available, hide / fallback impacted elements
- Reading mode / Reader view support
- Print-able
- Remove interactable / navigational elements
- Simplify layout and design
- Remove background images/colors
- Accessibility (read: https://webaim.org):
- Keyboard navigation
- Hotkeys when applicable
- Multilingual
- Contents can be available in any number of languages
- Language specific URLs, subdirectories with gTLD e.g: accords-library.com/fr/...
- Visitors can manually select their preferred language (which also affect the UI language)
- For each content, visitors can see which languages are available, and are able to temporarily see it in another language without changing their UI language.
- By default, the best matching language will be presented to the user:
1. Consider the visitors explicit preferences
2. Imply the visitors preferences using Accept-Language HTTP header
3. Consider the language-specific URL
4. If all fail, fallback to default language (English)
- Fast
- Barely any JS
- Simple design
- Responsive images
- Multiple image sizes provided (srcset and sizes attributes)
- Lazy loaded
- Space reservation to reduce Cumulative Layout Shift
- Use of efficient formats (mostly WebP) and meaningful quality settings
- Server side rendered (both good and bad for speed)
- Reduced data transfer
- Reduced client-side complexity
- Would require edge computing to reduce latency
- Astro built-in View transitions and client-side navigation
- Some data caching between the web server and CMS (to be improved)
- SEO
- Good defaults for the metadata and OpenGraph properties
- Each page can provide a custom title, description, thumbnail, video, audio to be used
- Each language variants are indexes seperately.
- Complexity
- The complexity should be moved away from public-facing parts of the codebase
- The CMS should handle most of the complexity:
- Check for data completeness and conformity
- Provides a ready-to-use type-safe SDK to the web framework
- The Web framework should only worry about presentation
- Handle different browsers
- Respect user preferences
- Handle user interactions
- On the client device, there should be minimal complexity
- Handle responsiveness
- Handle view transitions (if JS is available)
- Use of web standards: let the browser handle most of the client-side complexity
## Enhancement provided with JavaScript
- Background images
- Only start the fade-in animation once the image is fully loaded. Without this, the image can suddently appear during the animation (or even after the animation is over) and it doesn't look as nice.
- Parallax effect
- Navigation
- Smooth scrolling when using anchor links
- Loading animation when navigation takes more than 500ms
- View transitions on compatible browsers: when navigating, the next page has a fade-in animation.
- On image pages (scans, gallery, image files), allow the user to navigate to the previous or next image using keyboard arrows.
- On media pages (scans, images, audios, videos), provide a download button. This way, the user doesn't have to right-click -> "save media as..."
- Partial page reload
- Allow for temporary language switching on multilingual content.
- Tooltips
- Quicker access to user settings. Instead of going to a sepeare "settings" page, the user can set their favorite language, theme, and currency from any page.
- If a page has multiple parent pages, when the user click on the "Go back" button, it will open a tooltip with the list of parent pages. Right now, the parent pages are only displayed to noscript users if there is only one parent page.
- On the timeline, metadata such as credits, additional notes, language switching are not available to noscript users.
## Drawback of JavaScript
When going back in the navigation history, the page seems to load slower when JavaScript is enabled.
The parallax effect on background images is a bit demanding, it is disabled on mobiles and tablets to lessen the impact.
## Browser-specific tricks
### Dotted texture
A dotted texture is displayed on the page background. It uses `background-blend-mode` to blend the image with the background color. This blending mode doesn't seem to work on iOS devices. This dotted texture is currently disabled on iOS devices. Other alternatives could include:
- Removing the effet entirely
- Replacing the image with a transparent image (no need for blending)
- Replacing the image with a non-transparent image where the blending is baked-in
- Check if there are ways to make the blending work on iOS
### Parallax effect
A parallax effect is applied on the webpages' background image. This effect can be a bit demanding, it is disabled on mobiles and tablets to lessen the impact. Other alternatives could include:
- Removing the effet entirely
- Moving away from JavaScript and using CSS parallax tricks (transform 3D, sticky)
## CSS Utility classes ## CSS Utility classes
- `when-js`: only display element if JavaScript is available - `when-js`: only display element if JavaScript is available

View File

@ -6,6 +6,8 @@
## Short term ## Short term
- Display if a content has a source language
- [JSLess] Display if a content is available in more than one language
- Number of audio players seems limited (on Chrome and Firefox) - Number of audio players seems limited (on Chrome and Firefox)
- [RichTextContent] Handle relationship - [RichTextContent] Handle relationship
- [RichTextContent] Add autolink block support - [RichTextContent] Add autolink block support
@ -23,6 +25,7 @@
## Long term ## Long term
- Try using CSS instead of JS for parallax effect
- More data caching between the CMS and Astro - More data caching between the CMS and Astro
- [Folders] Support for nameless section - [Folders] Support for nameless section
- [Scripts] Can't run the scripts using node (ts-node?) - [Scripts] Can't run the scripts using node (ts-node?)

View File

@ -26,8 +26,8 @@ const {
{/* ------------------------------------------- HTML ------------------------------------------- */} {/* ------------------------------------------- HTML ------------------------------------------- */}
<Html openGraph={openGraph}> <Html openGraph={openGraph}>
{backgroundImage && <AppLayoutBackgroundImg img={backgroundImage} />}
<header> <header>
{backgroundImage && <AppLayoutBackgroundImg img={backgroundImage} />}
<Topbar parentPages={parentPages} hideHomeButton={hideHomeButton} /> <Topbar parentPages={parentPages} hideHomeButton={hideHomeButton} />
</header> </header>
<main><slot /></main> <main><slot /></main>

View File

@ -6,6 +6,7 @@ import type {
} from "src/shared/payload/payload-sdk"; } from "src/shared/payload/payload-sdk";
import { getRandomId } from "src/utils/random"; import { getRandomId } from "src/utils/random";
import { sizesToSrcset } from "src/utils/img"; import { sizesToSrcset } from "src/utils/img";
import { UAParser } from "ua-parser-js";
interface Props { interface Props {
img: EndpointImage | EndpointMediaThumbnail | EndpointScanImage; img: EndpointImage | EndpointMediaThumbnail | EndpointScanImage;
@ -22,45 +23,54 @@ const style = `
#${uniqueId} { #${uniqueId} {
mask-image: linear-gradient( to bottom, rgba(0 0 0 / 30%) 0%, transparent 100% ); mask-image: linear-gradient( to bottom, rgba(0 0 0 / 30%) 0%, transparent 100% );
} }
} }`; // Required to be done like this because we can't insert variables in media queries with Astro.
`;
const userAgent = Astro.request.headers.get("user-agent") ?? "";
const parser = new UAParser(userAgent);
const isParallaxEnabled =
parser.getDevice().type !== "mobile" && parser.getDevice().type !== "tablet";
--- ---
{/* ------------------------------------------- HTML ------------------------------------------- */} {/* ------------------------------------------- HTML ------------------------------------------- */}
<img <div>
id={uniqueId} <img
src={url} id={uniqueId}
srcset={sizesToSrcset(sizes)} src={url}
sizes="100vw" srcset={sizesToSrcset(sizes)}
width={width} sizes="100vw"
height={height} width={width}
loading="lazy" height={height}
class="when-no-print when-js" loading="lazy"
/> class="when-no-print"
<img />
id={uniqueId} </div>
src={url}
srcset={sizesToSrcset(sizes)}
sizes="100vw"
width={width}
height={height}
loading="lazy"
class="when-no-print when-no-js"
/>
{/* ------------------------------------------- CSS -------------------------------------------- */} {/* ------------------------------------------- CSS -------------------------------------------- */}
<style set:html={style} is:inline></style> <style set:html={style} is:inline></style>
<style> <style>
img { @keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
div {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
overflow: hidden;
z-index: -1;
}
img {
width: 100%; width: 100%;
height: auto; height: auto;
max-height: 100%; max-height: 100%;
@ -68,8 +78,6 @@ const style = `
object-fit: cover; object-fit: cover;
object-position: 50% 0; object-position: 50% 0;
z-index: -1;
mask-image: linear-gradient( mask-image: linear-gradient(
to bottom, to bottom,
rgba(0 0 0 / 30%) 0%, rgba(0 0 0 / 30%) 0%,
@ -80,19 +88,37 @@ const style = `
user-select: none; user-select: none;
&.when-js { animation: fadeIn 3s forwards;
opacity: 0;
transition: 3s opacity;
}
} }
</style> </style>
{/* ------------------------------------------- JS --------------------------------------------- */} {/* ------------------------------------------- JS --------------------------------------------- */}
<script define:vars={{ uniqueId }} is:inline> <script define:vars={{ uniqueId, isParallaxEnabled }} is:inline>
const element = document.getElementById(uniqueId); const element = document.getElementById(uniqueId);
element.style.animationPlayState = "paused";
element.addEventListener("load", () => { if (isParallaxEnabled) {
element.style.opacity = 1; window.backgroundParralaxElement = element;
}); }
element.addEventListener(
"load",
() => {
element.style.animationPlayState = "running";
},
{ once: true, passive: true }
);
</script>
<script>
const refreshParallax = () => {
if (!("backgroundParralaxElement" in window)) return;
if (!(window.backgroundParralaxElement instanceof HTMLElement)) return;
const parallaxAmount = window.scrollY * 0.4;
window.backgroundParralaxElement.style.transform = `translateY(${parallaxAmount}px)`;
};
document.addEventListener("scroll", refreshParallax, { passive: true });
</script> </script>

View File

@ -0,0 +1,111 @@
<!doctype html>
<html>
<body>
<div class="parallax">
<img src="https://dashboard.accords-library.com/images/home-background-image.webp" alt="" />
</div>
<div class="content-outer">
<div class="content-inner">
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sed risus in dui porta
elementum. Sed vehicula ante dignissim lacinia egestas. Integer eget massa tellus. Nunc
blandit neque enim, non faucibus nisl dignissim aliquam. Vestibulum et ipsum eget tellus
venenatis bibendum a at arcu. Quisque congue faucibus nisl, sed tristique eros suscipit
nec. Donec quis tincidunt urna. Sed urna dui, placerat aliquet pulvinar vitae, blandit sed
lorem. Curabitur et venenatis nibh, id feugiat nulla. Suspendisse ac mi diam. Nulla vitae
odio metus. Praesent id sagittis dolor, non efficitur ligula.
</p>
<p>
Fusce suscipit eget felis nec euismod. Maecenas odio lacus, aliquam sit amet libero eget,
posuere dignissim odio. Nunc porta elementum massa, nec facilisis tortor sodales in. Nulla
lacus felis, elementum ac urna a, fringilla ultrices ipsum. Pellentesque dapibus congue
fermentum. Praesent tempus risus eget augue viverra, sed tincidunt lacus posuere. Proin
rhoncus nisi libero, vel ultrices ipsum egestas lobortis. Fusce scelerisque accumsan nisi.
Quisque eget felis auctor, pulvinar elit vel, congue enim. Phasellus tincidunt felis
velit, in mollis purus tincidunt sed. Vestibulum scelerisque ipsum ac pellentesque
sagittis. Nam rutrum orci vitae enim volutpat consequat non id augue. Maecenas purus mi,
volutpat sit amet elit id, dapibus pellentesque arcu.
</p>
<p>
Etiam et bibendum tellus. Aliquam at tristique sapien. Mauris lacinia odio erat, et
gravida diam luctus convallis. Phasellus eget velit et arcu placerat lobortis. Curabitur
eleifend id elit quis lobortis. Praesent finibus sapien interdum, ultrices est eget,
faucibus erat. Aliquam neque nibh, fringilla laoreet auctor vestibulum, vestibulum quis
nisl.
</p>
<p>
Sed non metus ut massa pretium faucibus. Nam porta, lorem vel sodales sollicitudin, felis
mi dignissim urna, euismod dapibus ante massa vitae odio. Vivamus porttitor tristique
mauris, vel tempus ligula convallis non. Aliquam finibus dui nec nibh ornare, nec
scelerisque sem molestie. Aenean non leo quis massa scelerisque volutpat vel in dolor.
Curabitur vel massa nec tellus tempus tempor. Pellentesque dignissim ex nec augue viverra
scelerisque. Pellentesque eget tortor euismod, efficitur sem at, maximus ipsum.
</p>
<p>
Pellentesque ipsum sem, pretium non turpis eu, hendrerit eleifend neque. Vestibulum ante
ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Aenean et porta
urna, et lobortis enim. Aliquam erat volutpat. Nunc molestie consequat erat. Donec non
tempus orci. Sed vitae elit lorem. Aliquam erat volutpat.
</p>
</div>
</div>
</body>
</html>
<style>
html {
overflow: hidden;
}
body {
width: 100vw;
height: 100vh;
overflow-x: hidden;
overflow-y: auto;
perspective: 1px;
transform-style: preserve-3d;
}
div.parallax {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
transform-style: preserve-3d;
}
img {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: auto;
max-height: 100%;
object-fit: cover;
object-position: 50% 0;
z-index: -1;
mask-image: linear-gradient(
to bottom,
rgba(0 0 0 / 30%) 0%,
rgba(0 0 0 / 5%) 100vh,
rgba(0 0 0 / 5%) 80%,
transparent 100%
);
user-select: none;
transform: translateZ(-1px) scale(2);
}
</style>