blur placeholder on article images

This commit is contained in:
tmshv committed 2021-09-22 00:21:19 +03:00
1 parent df229b5e8c
commit 8260e3cf34
9 files changed
+1108 -81

No files matched your search

+1000 -10
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -28,6 +28,7 @@
"react-dom": "^17.0.1",
"react-icons": "^4.2.0",
"react-use": "^17.2.1",
"sharp": "^0.29.1",
"sitemap": "^1.5.0",
"typograf": "^6.11.1",
"use-media": "^1.4.0"
@@ -37,6 +38,7 @@
"@types/lodash": "^4.14.149",
"@types/node": "^12.12.34",
"@types/react": "^16.9.32",
"@types/sharp": "^0.29.2",
"eslint": "^7.1.0",
"feed": "^4.1.0",
"should": "^8.2.2",
+1
View File
@@ -112,6 +112,7 @@ const Index: NextPage<Props> = props => {
width={x.data.width}
height={x.data.height}
caption={x.data.caption}
blur={x.data.blur}
wide={x.wide}
/>
)
+4 -1
View File
@@ -36,11 +36,14 @@ export const Picture: React.FC<PictureProps> = memo(({ wide = false, ...props })
return (
<figure className={`${s.pic} ${layout} ${w ? s.wide : ''}`}>
<Image
className={s.img}
src={props.src}
alt={props.alt}
// alt={props.alt}
width={props.width}
height={props.height}
layout={'responsive'}
blurDataURL={props.blur}
placeholder={'blur'}
/>
{!props.caption ? null : (
<figcaption>{props.caption}</figcaption>
+5 -1
View File
@@ -16,8 +16,12 @@
max-width: 80%;
}
.pic img {
.img {
border-radius: var(--radius);
/* override nextjs blurred placeholder filter */
/* need for border radius */
filter: none !important;
}
.pic figcaption {
+8 -4
View File
@@ -20,8 +20,8 @@ export async function requestGet<T>(url: string, defaultResponse: T): Promise<T>
/**
* This wrapper prevents fetching api request in static export mode and production environment
* because nextjs call getInitialProps on the client side
*
* @param {*} fn
*
* @param {*} fn
*/
export function wrapInitialProps(fn: (ctx: NextPageContext) => void) {
return async (ctx: NextPageContext) => {
@@ -37,9 +37,13 @@ export function createApiUrl(path: string) {
return `https://api.tmshv.com/hudozka${path}`
}
export function apiGet<I, O>(factory: (response: I) => O) {
export type FactoryFunction<I, O> =
| ((response: I) => O)
| ((response: I) => Promise<O>)
export function apiGet<I, O>(factory: FactoryFunction<I, O>) {
return async (url: string, defaultResponse: O) => {
const res = await requestGet<I>(url, null)
const res = await requestGet<I>(url, null as any)
if (!res) {
return defaultResponse
}
+67 -65
View File
@@ -2,6 +2,7 @@ import { ImageDefinition, IMenu, IPage, ITag, PageCardDto, Token } from "@/types
import { asItem } from "./lib";
import { StrapiComponentEmbed, StrapiComponent, StrapiHome, StrapiPage, StrapiPageCard, StrapiTag, StrapiMedia, StrapiMenu } from "./types";
import { typograf, markdownToHtml } from 'src/lib/text'
import { encodeImageToBlurhash } from "./image";
const md = (text: string) => typograf(markdownToHtml(text))
@@ -40,70 +41,70 @@ export function createEmbed(component: StrapiComponentEmbed): Token {
}
}
export function createPageTokens(components: StrapiComponent[]): Token[] {
return components.map(component => {
switch (component.__component) {
case 'hudozka.text': {
return {
token: 'html',
data: md(component.text),
}
export async function createPageToken(component: StrapiComponent): Promise<Token> {
switch (component.__component) {
case 'hudozka.text': {
return {
token: 'html',
data: md(component.text),
}
case 'hudozka.image': {
return {
token: 'image',
wide: component.wide,
data: {
alt: component.caption,
caption: component.caption,
src: component.media.url,
width: component.media.width,
height: component.media.height,
}
}
}
case 'hudozka.document': {
return {
token: 'file',
data: {
url: component.media.url,
slug: 'jopa',
image_url: component.media.url,
file_url: component.media.url,
title: component.title,
file_size: component.media.size * 1000,
// file_size: component.media.size,
file_format: component.media.mime,
}
}
}
case 'hudozka.embed': {
return createEmbed(component)
}
case 'hudozka.card-grid': {
return {
token: 'grid',
data: {
items: component.items
.filter(Boolean)
.map(createCardGrid)
}
}
}
default:
return {
token: 'text',
data: `
${JSON.stringify(component)}
`,
}
}
})
case 'hudozka.image': {
const src = component.media.formats?.large?.url ?? component.media.url
const thumb = component.media.formats?.thumbnail?.url ?? component.media.url
const blur = await encodeImageToBlurhash(thumb)
return {
token: 'image',
wide: component.wide,
data: {
alt: component.caption,
caption: component.caption,
src,
width: component.media.width,
height: component.media.height,
blur,
}
}
}
case 'hudozka.document': {
return {
token: 'file',
data: {
url: component.media.url,
slug: 'jopa',
image_url: component.media.url,
file_url: component.media.url,
title: component.title,
file_size: component.media.size * 1000,
// file_size: component.media.size,
file_format: component.media.mime,
}
}
}
case 'hudozka.embed': {
return createEmbed(component)
}
case 'hudozka.card-grid': {
return {
token: 'grid',
data: {
items: component.items
.filter(Boolean)
.map(createCardGrid)
}
}
}
default:
return {
token: 'text',
data: ` ${JSON.stringify(component)}`,
}
}
}
export function createMenu(res: StrapiMenu): IMenu[] {
@@ -118,7 +119,7 @@ export function createMenu(res: StrapiMenu): IMenu[] {
}, ...items]
}
export function createPage(res: StrapiPage | StrapiPage[]): IPage | null {
export async function createPage(res: StrapiPage | StrapiPage[]): IPage | null {
const item = asItem(res)
if (!item) {
return null
@@ -132,6 +133,8 @@ export function createPage(res: StrapiPage | StrapiPage[]): IPage | null {
href: `/tags/${tag.slug}`,
}))
const tokens = await Promise.all(item.content.map(createPageToken))
return {
title: item.title,
description: item.excerpt,
@@ -147,8 +150,7 @@ export function createPage(res: StrapiPage | StrapiPage[]): IPage | null {
data: md(`# ${item.title}`),
},
...createPageTokens(item.content)
.filter(Boolean),
...tokens.filter(Boolean),
],
tags,
// breadcrumb?: [],
+20
View File
@@ -0,0 +1,20 @@
import sharp from "sharp"
export async function encodeImageToBlurhash(url: string) {
const res = await fetch(url)
if (!res.ok) {
return undefined
}
const data = await res.arrayBuffer()
const buffer = Buffer.from(data)
const t = sharp(buffer)
.raw()
.resize(8, 8, { fit: "inside" })
.toFormat('jpeg')
const { data: out, info } = await t.toBuffer({ resolveWithObject: true })
return `data:image/jpeg;base64,${out.toString('base64')}`
}
+1
View File
@@ -104,6 +104,7 @@ export type Pic = {
width: number
height: number
caption?: string
blur?: string
}
export type TextToken = {