avatar.jsx
1 import './avatar.css'; 2 3 import { useRef } from 'preact/hooks'; 4 5 import mem from '../utils/mem'; 6 7 const SIZES = { 8 s: 16, 9 m: 20, 10 l: 24, 11 xl: 40, 12 xxl: 50, 13 xxxl: 64, 14 }; 15 16 const alphaCache = {}; 17 18 const canvas = window.OffscreenCanvas 19 ? new OffscreenCanvas(1, 1) 20 : document.createElement('canvas'); 21 const ctx = canvas.getContext('2d', { 22 willReadFrequently: true, 23 }); 24 25 function Avatar({ url, size, alt = '', squircle, ...props }) { 26 size = SIZES[size] || size || SIZES.m; 27 const avatarRef = useRef(); 28 const isMissing = /missing\.png$/.test(url); 29 return ( 30 <span 31 ref={avatarRef} 32 class={`avatar ${squircle ? 'squircle' : ''} ${ 33 alphaCache[url] ? 'has-alpha' : '' 34 }`} 35 style={{ 36 width: size, 37 height: size, 38 }} 39 title={alt} 40 {...props} 41 > 42 {!!url && ( 43 <img 44 src={url} 45 width={size} 46 height={size} 47 alt={alt} 48 loading="lazy" 49 decoding="async" 50 crossOrigin={ 51 alphaCache[url] === undefined && !isMissing 52 ? 'anonymous' 53 : undefined 54 } 55 onError={(e) => { 56 if (e.target.crossOrigin) { 57 e.target.crossOrigin = null; 58 e.target.src = url; 59 } 60 }} 61 onLoad={(e) => { 62 if (avatarRef.current) avatarRef.current.dataset.loaded = true; 63 if (alphaCache[url] !== undefined) return; 64 if (isMissing) return; 65 try { 66 // Check if image has alpha channel 67 const { width, height } = e.target; 68 if (canvas.width !== width) canvas.width = width; 69 if (canvas.height !== height) canvas.height = height; 70 ctx.drawImage(e.target, 0, 0); 71 const allPixels = ctx.getImageData(0, 0, width, height); 72 // At least 10% of pixels have alpha <= 128 73 const hasAlpha = 74 allPixels.data.filter((pixel, i) => i % 4 === 3 && pixel <= 128) 75 .length / 76 (allPixels.data.length / 4) > 77 0.1; 78 if (hasAlpha) { 79 // console.log('hasAlpha', hasAlpha, allPixels.data); 80 avatarRef.current.classList.add('has-alpha'); 81 } 82 alphaCache[url] = hasAlpha; 83 ctx.clearRect(0, 0, width, height); 84 } catch (e) { 85 // Silent fail 86 alphaCache[url] = false; 87 } 88 }} 89 /> 90 )} 91 </span> 92 ); 93 } 94 95 export default mem(Avatar);