Slide 1

Slide 1 text

#burikaigi_m もう一歩進めたい OG画像の動的生成 BuriKaigi 2024 @mottox2

Slide 2

Slide 2 text

#burikaigi_m @mottox2 UIデザインとウェブフロントエンド

Slide 3

Slide 3 text

#burikaigi_m

Slide 4

Slide 4 text

#burikaigi_m

Slide 5

Slide 5 text

#burikaigi_m OG画像 E サイトの情報を伝えるためのOpen Graphというプロトコ P E その中の画像をOG画像と呼んでいh E 特にXではtitleやdescriptionより画像の主張が強いの で、 各サイトやサービスが工夫を凝らしている

Slide 6

Slide 6 text

#burikaigi_m @vercel/ogとは?

Slide 7

Slide 7 text

#burikaigi_m @vercel/og ( JSXからsvgを生成するsatoriとsvgからpngが画像に変 換するresvgをVercel上でまとめて使えるようにしたも) ( Next.jsに組み込まれていて簡単に利用できる JSX satori resvg SVG PNG

Slide 8

Slide 8 text

#burikaigi_m 例えば < {{ height: , width: , display: , flexDirection: , alignItems: , justifyContent: , backgroundColor: , fontSize: , fontWeight: , }} > < {{ margin: }} > < > > > < {{ marginTop: }}>Hello, World > > div svg path path svg div div div style width viewBox fill style d style = = = = = = = '100%' '100%' 'flex' 'column' 'center' 'center' '#fff' "75" "0 0 75 65" "#000" '0 75px' "M37.59.25l36.95 64H.64l36.95-64z" 32 600 40

Slide 9

Slide 9 text

#burikaigi_m 例えば < < > < /> > < /> < > < /> > < > < /> > < < > < /> > < > < /> > svg mask rect mask rect rect mask rect mask image mask rect mask mask rect mask width height viewBox xmlns id x y width height fill x y width height fill id x y width height id x y width height fill x y width height href preserveAspectRatio clip-path mas id x y width height fill id x y width height fill = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = "800" "400" "0 0 800 400" "http://w "satori_om-id" "0" "0" "800" "400" "#fff" "0" "0" "800" "400" "#fff" "satori_cp-id-0" "363" "128" "75" "65" "satori_om-id-0" "363" "128" "75" "65" "#fff" "363" "128" "75" "65" "data:image/svg+xml;utf8,%3Csvg fill=%22%23000%22 xmlns=% "none" "url(#satori_cp-id-0)" "satori_om-id-0-0" "363" "128" "0" "65" "#fff" "satori_om-id-1" "304" "233" "193" "39" "#fff" clipPath clipPath

Slide 10

Slide 10 text

#burikaigi_m 例えば

Slide 11

Slide 11 text

#burikaigi_m @vercel/ogの嬉しさ ・ しんどさ „ 世の中の画像生成手法は座標を考えながら使う必要が あるが@vercel/ogではほとんど考えなく て良s „ 一方、 SatoriはHTMLのサブセッ トであり、 結構できなこと が多い

Slide 12

Slide 12 text

#burikaigi_m Playground https://satori-playground.vercel.app

Slide 13

Slide 13 text

#burikaigi_m サポート状況の確認 https://github.com/vercel/satori

Slide 14

Slide 14 text

#burikaigi_m 無事画像が作れるようになった ただ、 画像を作るだけではつまらない 自動作成された画像感を減らしたい

Slide 15

Slide 15 text

#burikaigi_m 自動作成されてる感はどこから来るのか? デザイン段階で決まってC 9 画像が浮いていC 9 フォントがデフォルト、 文字詰めが適b 9 Webっぽいグリッドを感じる

Slide 16

Slide 16 text

#burikaigi_m 標準で使われているフォントを使うと、 ダサさがある d 標準フォントでは太字指定が無視され@ d「」 、 。 などの役物の空きが気になる

Slide 17

Slide 17 text

#burikaigi_m デフォルト(Roboto) YakuHanJP

Slide 18

Slide 18 text

#burikaigi_m 標準で使われているフォントを使うと、 ダサさがある € おすすめは qracさんが作られている YakuHanJP MergedのSemiboldかBolc € Noto Sans JPに対して、 半角サイズの役物 (句読点と 鉤括弧等の文字) を混ぜたフォント

Slide 19

Slide 19 text

#burikaigi_m 文字づめがされていない v 文字詰めをやるCSS `font-feature-settings: ‘palt’` はsatoriがサポートしていないため動かないi v → 場合によって代わりに `letter-spacing: -0.02em` で全体的に詰めておくのもあり こんにちは世界 こんにちは世界 ツメなし ツメあり

Slide 20

Slide 20 text

#burikaigi_m 画像の使い方 そのまま使うとブログのアイキャッチ感があふれ` B 特定の形にトリミングしてみ` B 上からグラデーションの座布団を引G B ぼかしてみる

Slide 21

Slide 21 text

#burikaigi_m 視覚調整 Vercel 正しい位置に配置すると目の錯覚で小さく見える 世の中のロゴやフォントにはこの調整が入っている

Slide 22

Slide 22 text

#burikaigi_m 視覚調整の対応 R ウェブでは保守性のためスキップすることが多3 R 画像を作るならやるべ$ R マイナス側のマージンをつければOK

Slide 23

Slide 23 text

#burikaigi_m

Slide 24

Slide 24 text

#burikaigi_m 大切なこと R 何ができて、 何ができないかを知っておくこと ・ 伝えるこT R Web技術で作れることと、 Webのように作ることを混同 しない方がよ1 R レスポンシブとか考えなく てよく て、 特定のサイズに特化し たスタイリングを行えばよい

Slide 25

Slide 25 text

#burikaigi_m もう一歩、 プログラムで作る意味を見出したい

Slide 26

Slide 26 text

#burikaigi_m 何ができそうか? S ランダムに背景を選択すF S SVGを利用した動的なビジュアルを作F S 画像の色を考慮したビジュアルを作る

Slide 27

Slide 27 text

#burikaigi_m KODANSHAtech コーポレートブログの例

Slide 28

Slide 28 text

#burikaigi_m ランダムな背景を用意する S 真っ先に思いつくであろう活用U S ランダムに背景画像を切り替える実… S 完全ランダムにしてしまうと、 毎回変わってしまうので、 URLに対して一意にするための実装をするとよw S シードを設定できるランダム関数の実装

Slide 29

Slide 29 text

#burikaigi_m class constructor = % if <= += return = * % return - / let = new { ( ) { .seed seed ; ( .seed ) .seed ; } () { .seed .seed ; } () { ( . () ) ; } } rng ( ); console. (rng. ()); SeededRandom next nextFloat next SeededRandom log nextFloat seed this 2147483647 this 0 this 2147483646 this this 16807 2147483647 this 1 2147483646 12345 // 0から1の範囲で乱数を返す // 使用例 // シード値を設定 // 常に同じシード値で同じ乱数列を生成 generated by ChatGPT 同じURLでは同じ乱数が生成されるようにする

Slide 30

Slide 30 text

#burikaigi_m SVGを利用した動的なビジュアルを作る E vercel/ogはpngを生成する中間状態にSVGを利用して いるので、 SVGの自由度が高めになっていV E SVGを出し分けるJSXを利用して、 (普通なら実現しにく い) 動的なビジュアルを作る

Slide 31

Slide 31 text

#burikaigi_m

Slide 32

Slide 32 text

#burikaigi_m SVGでクリエイティブコーディング return ... = / / / = = = = = = = = < return ... = / / / = = = = = = = < return ... = = / / / = = / / = = / / / < { }> < { blockSize blockSize blockSize b < { / } { / } { / } { < { / } { / } { / } { } > // T 270 if (value ) < { }> < { blockSize blockSize blockSize blockSize < { / } { / } { / } { } < { / } { / } { } /> > // X if (value ) < { }> < { } { blockSize blockSize blockSize bloc {/* < { } { blockSize blockSize blockSize < { } { blockSize blockSize blockSize bloc > // O 2 2 2 2 2 2 2 2 2 0.25 2 2 2 2 2 2 2 2 0.55 2 2 2 2 2 2 2 2 g groupProps path d rect x blockSize y blockSize width blockSize height blockSize rect y blockSize width blockSize height blockSize fill g g groupProps path d rect y blockSize width blockSize height blockSize fill rect width blockSize height blockSize fill g g groupProps path fill d path fill d path fill d g color color color color color color `M0 ${ } L 0 0 A ${ } ${ } 0 0 1 ${ `M${ } 0 L ${ } 0 A ${ } ${ `m${ } ${ } ${ } ${ `M${ } ${ } 0 0h${ }L${ `m${ } ${ } ${ }-${

Slide 33

Slide 33 text

#burikaigi_m return ... = / / / = = = = = = = = < return ... = / / / = = = = = = = < return ... = = / / / = = / / = = / / / < { }> < { blockSize blockSize blockSize b < { / } { / } { / } { < { / } { / } { / } { } > // T 270 if (value ) < { }> < { blockSize blockSize blockSize blockSize < { / } { / } { / } { } < { / } { / } { } /> > // X if (value ) < { }> < { } { blockSize blockSize blockSize bloc {/* < { } { blockSize blockSize blockSize < { } { blockSize blockSize blockSize bloc > // O 2 2 2 2 2 2 2 2 2 0.25 2 2 2 2 2 2 2 2 0.55 2 2 2 2 2 2 2 2 g groupProps path d rect x blockSize y blockSize width blockSize height blockSize rect y blockSize width blockSize height blockSize fill g g groupProps path d rect y blockSize width blockSize height blockSize fill rect width blockSize height blockSize fill g g groupProps path fill d path fill d path fill d g color color color color color color `M0 ${ } L 0 0 A ${ } ${ } 0 0 1 ${ `M${ } 0 L ${ } 0 A ${ } ${ `m${ } ${ } ${ } ${ `M${ } ${ } 0 0h${ }L${ `m${ } ${ } ${ }-${ SVGでクリエイティブコーディング h vercel/ogでは画像アウトプット前の中間状態にSVGを 使っているので、 それを想定して書G h これらのパターンをランダムに並べている

Slide 34

Slide 34 text

#burikaigi_m 画像の色を考慮したビジュアルを作る 富山で食べた寿司がうまかった

Slide 35

Slide 35 text

#burikaigi_m 画像の色を考慮したビジュアルを作る 富山で食べた寿司がうまかった 色を考慮しない場合

Slide 36

Slide 36 text

#burikaigi_m 画像の色を考慮したビジュアルを作る 富山で食べた寿司がうまかった 特徴色を取得 輝度を特定の値に変更 グラデーションの値に利用 hsl(34, 28, 57) hsl(34, 28, 30)

Slide 37

Slide 37 text

#burikaigi_m 画像の色を考慮したビジュアルを作る ‘ 画像の各ピクセル情報から、 その画像の特徴的な色を取 り出し、 その色を画像の要素として利用すX ‘ 取り出した色をそのまま使うのではなく、 輝度を落として 利用すると使いやすa ‘ 画像処理は重いのでEdgeでは動かせず、 従来のサー バーレス上で動作、 または事前に保存しておく必要あり

Slide 38

Slide 38 text

#burikaigi_m 画像の色を考慮したビジュアルを作る import from import from import from const = await const = await const = new const = const = new const = const = const = jpeg { FastAverageColor } ; { rgb2hsl, hsl2rgb } (imageUrl) image. () (image) jpeg. (uint8Array) (); [ , , , ] fac. (decoded.data) [ , , ] (r, g, b) (h, s, ) 'jpeg-js' 'fast-average-color' './utils' image imageArray uint8Array decoded fac r g b a h s l themeColor 0.3 fetch arrayBuffer Uint8Array decode FastAverageColor getColorFromArray4 rgb2hsl hsl2rgb

Slide 39

Slide 39 text

#burikaigi_m まとめ c Web技術で作れることと、 Webのように作ることを混同 しない方がよe c エンジニアリング的な視点とデザイナー的な視点を混ぜ て、 ちょっと凝った画像生成ができるかもしれない

Slide 40

Slide 40 text

#burikaigi_m Thank you!