WIP PWA for Grain
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add grain-image atom with lazy loading

+80
+80
src/components/atoms/grain-image.js
··· 1 + import { LitElement, html, css } from 'lit'; 2 + 3 + export class GrainImage extends LitElement { 4 + static properties = { 5 + src: { type: String }, 6 + alt: { type: String }, 7 + aspectRatio: { type: Number }, 8 + _loaded: { state: true } 9 + }; 10 + 11 + static styles = css` 12 + :host { 13 + display: block; 14 + position: relative; 15 + overflow: hidden; 16 + background: var(--color-bg-elevated); 17 + } 18 + .container { 19 + position: relative; 20 + width: 100%; 21 + } 22 + .spacer { 23 + display: block; 24 + width: 100%; 25 + } 26 + img { 27 + position: absolute; 28 + top: 0; 29 + left: 0; 30 + width: 100%; 31 + height: 100%; 32 + object-fit: cover; 33 + opacity: 0; 34 + transition: opacity 0.3s ease; 35 + } 36 + img.loaded { 37 + opacity: 1; 38 + } 39 + .placeholder { 40 + position: absolute; 41 + top: 0; 42 + left: 0; 43 + width: 100%; 44 + height: 100%; 45 + background: linear-gradient( 46 + 135deg, 47 + var(--color-bg-elevated) 0%, 48 + var(--color-bg-secondary) 100% 49 + ); 50 + } 51 + `; 52 + 53 + constructor() { 54 + super(); 55 + this.aspectRatio = 1; 56 + this._loaded = false; 57 + } 58 + 59 + #handleLoad() { 60 + this._loaded = true; 61 + } 62 + 63 + render() { 64 + return html` 65 + <div class="container"> 66 + <svg class="spacer" viewBox="0 0 1 ${1 / this.aspectRatio}"></svg> 67 + <div class="placeholder"></div> 68 + <img 69 + src=${this.src || ''} 70 + alt=${this.alt || ''} 71 + class=${this._loaded ? 'loaded' : ''} 72 + loading="lazy" 73 + @load=${this.#handleLoad} 74 + > 75 + </div> 76 + `; 77 + } 78 + } 79 + 80 + customElements.define('grain-image', GrainImage);