Added parallax to background image
This commit is contained in:
parent
2e0b586569
commit
967ccf3184
131
README.md
131
README.md
|
@ -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 visitor’s explicit preferences
|
||||||
|
2. Imply the visitor’s 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
|
||||||
|
|
3
TODO.md
3
TODO.md
|
@ -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?)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue