Vistas previas de imágenes integradas con funciones Sharp, BlurHash y Lambda trucos CSS
¿No odia cuando carga un sitio web o una aplicación web, se muestra algún contenido? y luego cargando algunas imágenes, ¿provocando el desplazamiento del contenido? Se llama conversión de contenido y puede conducir a una experiencia de usuario increíblemente molesta para los visitantes.
Anteriormente escribí sobre cómo resolver esto con Suspense de React, que no permite que la interfaz de usuario se cargue hasta que ingresan las imágenes. Esto resuelve el problema de volver a convertir el contenido, pero a expensas del rendimiento. El usuario no puede ver ningún contenido hasta que ingresen las imágenes.
¿No sería bueno si pudiéramos tener lo mejor de ambos mundos: evitar que el contenido vuelva a girar sin hacer que el usuario espere las imágenes? Esta publicación pasará por la generación de vistas previas borrosas de imágenes y su visualización inmediata, con imágenes reales que se muestran sobre la vista previa cuando ingresan.
¿Te refieres a JPEG progresivo?
Usted se estará preguntando si voy a hablar de JPEG progresivo, que es una codificación alternativa que hace que las imágenes se reproduzcan inicialmente (a tamaño completo y borrosas) y luego se refinan gradualmente a medida que llegan los datos hasta que todo se procesa correctamente.
Esto parece una gran solución hasta que entras en algunos de los detalles. Recodificar sus imágenes como JPEG progresivos es relativamente fácil; hay complementos para Afilado esto funcionará para ti. Desafortunadamente, todavía tienes que esperar. algunos desde los bytes de sus imágenes para venir en el cable hasta que incluso se muestre una vista previa borrosa de su imagen, momento en el cual su contenido se convertirá ajustándose al tamaño de la vista previa de la imagen.
Puede buscar un evento para indicar que la vista previa de la imagen inicial se ha cargado, pero actualmente no existe y las soluciones son... no es perfecto.
Veamos dos alternativas para esto.
Las bibliotecas que usaremos
Antes de comenzar, me gustaría enumerar las versiones de las bibliotecas que usaré para esta publicación:
La mayoría de nosotros estamos acostumbrados a usar <img />
etiquetas proporcionando un src
un atributo que es una URL a un lugar en Internet donde existe nuestra imagen. Pero también podemos ofrecer una Codifique en Base64 una imagen y simplemente ajústela. no lo haríamos usualmente Quiero hacer esto porque estas cadenas Base64 pueden volverse enormes para las imágenes e incrustarlas en nuestros paquetes de JavaScript puede causar una gran hinchazón.
Pero, ¿qué pasa si cuando procesamos nuestras imágenes (para cambiar el tamaño, ajustar la calidad, etc.), también creamos una versión borrosa y de baja calidad de nuestra imagen y usamos la codificación Base64? ese? El tamaño de esta vista previa de imagen en Base64 será significativamente menor. Podríamos guardar esta cadena de vista previa, ponerla en nuestro paquete de JavaScript y mostrarla incrustada hasta que nuestra imagen real termine de cargarse. Esto mostrará una vista previa borrosa de nuestra imagen tan pronto como se cargue la imagen. Cuando la imagen real termina de cargarse, podemos ocultar la vista previa y mostrar la imagen real.
Veamos cómo.
Generar nuestra reseña
Por ahora, echemos un vistazo Jimque no tiene dependencias en cosas como node-gyp
y se puede instalar y utilizar en Lambda.
Aquí hay una función (sin manejo ni registro de errores) que usa Jimp para procesar la imagen, cambiar su tamaño y luego crear una vista previa de imagen borrosa:
function resizeImage(src, maxWidth, quality) {
return new Promise<ResizeImageResult>(res => {
Jimp.read(src, async function (err, image) {
if (image.bitmap.width > maxWidth) {
image.resize(maxWidth, Jimp.AUTO);
}
image.quality(quality);
const previewImage = image.clone();
previewImage.quality(25).blur(8);
const preview = await previewImage.getBase64Async(previewImage.getMIME());
res({ STATUS: "success", image, preview });
});
});
}
voy a usar para esta publicación esta imagen fue proporcionada por Flickr Commons:
Así es como se ve la visualización:
Si desea echar un vistazo más de cerca, aquí está la misma vista previa en un CódigoSandbox.
Obviamente, esta codificación de vista previa no es pequeña, pero de nuevo, no es nuestra imagen; las imágenes más pequeñas darán vistas previas más pequeñas. Mida también un perfil para su propio caso de uso para ver qué tan viable es esta solución.
Ahora podemos enviar esta imagen para obtener una vista previa desde nuestra capa de datos, junto con la URL de la imagen real y cualquier otro dato relacionado. Podemos mostrar inmediatamente la visualización de la imagen y cuando se carga la imagen real, podemos intercambiarla. Aquí hay algunos códigos React (simplificados) para hacer esto:
const Landmark = ({ url, preview = "" }) => {
const [loaded, setLoaded] = useState(false);
const imgRef = useRef<HTMLImageElement>(null);
useEffect(() => {
// make sure the image src is added after the onload handler
if (imgRef.current) {
imgRef.current.src = url;
}
}, [url, imgRef, preview]);
return (
<>
<Preview loaded={loaded} preview={preview} />
<img
ref={imgRef}
onLoad={() => setTimeout(() => setLoaded(true), 3000)}
style={{ display: loaded ? "block" : "none" }}
/>
</>
);
};
const Preview: FunctionComponent<LandmarkPreviewProps> = ({ preview, loaded }) => {
if (loaded) {
return null;
} else if (typeof preview === "string") {
return <img key="landmark-preview" alt="Landmark preview" src={preview} style={{ display: "block" }} />;
} else {
return <PreviewCanvas preview={preview} loaded={loaded} />;
}
};
no te preocupes por PreviewCanvas
componente todavía. y no Preocúpese por el hecho de que cosas como el cambio de URL no se informan.
Tenga en cuenta que configuramos el componente de imagen src
Después onLoad
manipulador para asegurarse de que se activa. Mostramos la visualización y cuando se carga la imagen real, la intercambiamos.
Mejora las cosas con BlurHash
La vista previa de la imagen que vimos antes puede no ser lo suficientemente pequeña para enviarla con nuestro paquete de JavaScript. Y estas cadenas Base64 no gzip bien. Dependiendo de cuántas de estas imágenes tenga, esto puede o no ser lo suficientemente bueno. Pero si desea comprimir cosas aún más pequeñas y está listo para hacer un poco más de trabajo, hay una biblioteca maravillosa llamada BlurHash.
BlurHash genera vistas previas increíblemente pequeñas utilizando la codificación Base83. La codificación Base83 le permite comprimir más información en menos bytes, lo que es parte de la forma en que mantiene las visualizaciones tan pequeñas. 83 puede parecer un número arbitrario, pero README arroja algo de luz sobre esto:
En primer lugar, 83 parece ser la cantidad de caracteres ASCII bajos que puede encontrar que son seguros para usar en todos los JSON, HTML y shells.
En segundo lugar, 83 * 83 está muy cerca y un poco más que 19 * 19 * 19, lo que lo hace ideal para codificar tres componentes de CA en dos caracteres.
README también indica cómo Signal y Mastodon usan BlurHash.
Veámoslo en acción.
generando blurhash
visualizaciones
Para ello tendremos que utilizar Afilado biblioteca.
Nota
Para generar la tuya blurhash
vistas previas, probablemente querrá ejecutar alguna función sin un servidor para procesar sus imágenes y generar vistas previas. Usaré AWS Lambda, pero cualquier alternativa debería funcionar.
Solo tenga cuidado con los límites de tamaño máximo. Los archivos binarios que instala Sharp agregan alrededor de 9 MB al tamaño de la función sin servidor.
Para ejecutar este código en AWS Lambda, deberá instalar la biblioteca de la siguiente manera:
"install-deps": "npm i && SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm i --arch=x64 --platform=linux sharp"
Y asegúrese de no agrupar para asegurarse de que todos los archivos binarios se envíen a su Lambda. Esto afectará el tamaño de la implementación de Lambda. Solo Sharp tendrá unos 9 MB, lo que no será bueno para un arranque en frío. El código que verá a continuación está en Lambda, que simplemente se ejecuta periódicamente (sin esperar una interfaz de usuario), generando blurhash
visualizaciones.
Este código observará el tamaño de la imagen y creará un blurhash
visualización:
import { encode, isBlurhashValid } from "blurhash";
const sharp = require("sharp");
export async function getBlurhashPreview(src) {
const image = sharp(src);
const dimensions = await image.metadata();
return new Promise(res => {
const { width, height } = dimensions;
image
.raw()
.ensureAlpha()
.toBuffer((err, buffer) => {
const blurhash = encode(new Uint8ClampedArray(buffer), width, height, 4, 4);
if (isBlurhashValid(blurhash)) {
return res({ blurhash, w: width, h: height });
} else {
return res(null);
}
});
});
}
Nuevamente, eliminé todo el manejo de errores y el registro para mayor claridad. Vale la pena señalar la llamada a ensureAlpha
Esto asegura que cada píxel tenga 4 bytes, uno para RGB y Alpha.
Jimp carece de este método, así que usamos Sharp; si alguien sabe lo contrario, por favor deje un comentario.
También tenga en cuenta que no solo mantenemos la cadena para la visualización, sino también el tamaño de la imagen, que tendrá sentido en un momento.
El verdadero trabajo ocurre aquí:
const blurhash = encode(new Uint8ClampedArray(buffer), width, height, 4, 4);
estamos llamando blurhash
's encode
método, transmitiendo nuestra imagen y las dimensiones de la imagen. Los dos últimos argumentos son componentX
y componentY
que desde mi comprensión de la documentación parece controlar cuántos pases blurhash
hace sobre nuestra imagen, añadiendo cada vez más detalles. Los valores aceptables son del 1 al 9 (ambos inclusive). Según mis propias pruebas, 4 es un lindo lugar que da los mejores resultados.
Veamos qué produce esto para la misma imagen:
{
"blurhash" : "UAA]{ox^0eRiO_bJjdn~9#M_=|oLIUnzxtNG",
"w" : 276,
"h" : 400
}
¡Este es un pequeño increíble! El compromiso es este utilizando esta revisión es un poco más interesante.
En principio, tenemos que llamar blurhash
's decode
y trazar nuestra vista general de la imagen en un canvas
etiqueta. Esto es lo que PreviewCanvas
componente ha hecho antes y por qué lo mostramos, si el tipo de nuestra revisión no es una cadena: nuestro blurhash
las visualizaciones usan un objeto completo, que contiene no solo la cadena para la visualización, sino también las dimensiones de la imagen.
Miremos el nuestro PreviewCanvas
componente:
const PreviewCanvas: FunctionComponent<CanvasPreviewProps> = ({ preview }) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
useLayoutEffect(() => {
const pixels = decode(preview.blurhash, preview.w, preview.h);
const ctx = canvasRef.current.getContext("2d");
const imageData = ctx.createImageData(preview.w, preview.h);
imageData.data.set(pixels);
ctx.putImageData(imageData, 0, 0);
}, [preview]);
return <canvas ref={canvasRef} width={preview.w} height={preview.h} />;
};
No está pasando mucho aquí. Descodificamos nuestra revisión y luego activamos algunas API de Canvas muy específicas.
Veamos cómo se ven las vistas previas de imágenes:
En cierto sentido, es menos detallado que nuestras visualizaciones anteriores. Pero también encontré que son un poco más suaves y menos pixelados. Y ocupan una pequeña parte del tamaño.
Pruebe y use lo que funcione mejor para usted.
resumiendo
Hay muchas maneras de evitar que el contenido se vuelva a convertir mientras sus imágenes se cargan en la web. Un enfoque es evitar que se muestre la interfaz de usuario hasta que ingresen las imágenes. La desventaja es que su usuario espera más tiempo por el contenido.
Una buena posición intermedia es mostrar inmediatamente una vista previa de la imagen y cambiar la imagen real cuando se carga. Esta publicación lo lleva a través de dos formas de hacer esto: generar versiones degradadas y borrosas de una imagen usando una herramienta como Sharp y usar BlurHash para generar una vista previa extremadamente pequeña codificada en Base83.
¡Buena codificación!
Deja una respuesta