Open33

スタイリング・フロントエンド豆知識集

ピン留めされたアイテム
morit4ryomorit4ryo

主にはNext.js 14・Typescript・TailwindCSS・Storybook環境下で作業する時に、ちょっとした調べ物をしたり、軽くつまづいたことなどをメモしていきます。
需要がありそうなものなどは、そのうち別途記事に切り出します。
※そろそろ増えてきたのでTailwindCSSだけのスクラップに移行したい

morit4ryomorit4ryo

svgにimg要素のalt的機能を持たせる

https://blog.nasbi.jp/programming/frontend/html/svg-alt/
要約:svgにimg要素のalt的機能を持たせたい時は、

<svg role='img' aria-label='説明'></svg>
morit4ryomorit4ryo

.sr-only(表示はされないがスクリーンリーダーには認識される)というTailwindCSSのクラスがあるので、用途によっては別途要素を作ってそちらにsr-onlyを適用する手がありそう

morit4ryomorit4ryo

逆(表示するがスクリーンリーダーに読ませない)のクラスはない(というかそもそもCSSで設定できる範疇を超えているはず)が、別途設定した上でaria-hiddenがtrueの時のみクラスを与えることができるaria-hidden:が存在する

morit4ryomorit4ryo
morit4ryomorit4ryo

Twitter(X)において、ハッシュタグを複数書きたい時は

hashtags=alpha,beta

のようにカンマで区切って書く

morit4ryomorit4ryo

Twitter(X)のシェアURLについて、最新はshare?ではなくintent/tweet?となる
またLINEについて、こちらの情報によると
https://kojole.hatenablog.com/entry/2018/09/19/113840
公式ドキュメントには記載されていないが、ブラウザからであれば、share?text=でテキストも共有できるとのこと
https://developers.line.biz/ja/faq/tags/sp-share/#is-it-possible-to-share-text-with-url

Q「LINEで送る」ボタンでURLと一緒にテキストもシェアできますか?
カスタムアイコンを使って「LINEで送る」ボタンを作成する場合、URLと一緒に任意のテキストをシェアするよう設定できます。詳しくは、「カスタムアイコンを使用」を参照してください。LINE公式アイコンを使用する場合は、URLと一緒にテキストをシェアすることはできません。

morit4ryomorit4ryo

モバイルファーストでバナーを作成するときの適切な解像度の考え方

https://bindup.jp/camp/design/graphic/14927

デザイン・ワイヤーだと実寸で作っているが、解像度を考えると幅・高さともに倍(=面積は4倍)くらいで書き出した方が良さそう
背景画像などと違い、画面に表示されるサイズが固定となるバナーの作成依頼をするために調べた

morit4ryomorit4ryo

Next.js 13以降のImageの設定方法

https://ebisu.com/note/next-image-migration/

TailwindCSSと組み合わせる場合、style部分はclassNameに置き換えてよいので、以下のように書くことができる。

<Image
  src="/rocket.jpg"
  alt="空飛ぶロケット"
  width={1980}
  height={1150}
  sizes="100vw"
  className={'w-full h-auto'} // styleを書き換え
/>

layout="responsive"(サイズを可変にする画像)の場合から引用、className部分のみ書き換え)

morit4ryomorit4ryo

画面幅に応じて表示文字数を調整したい時は、TailwindCSSのline-clampを使うと楽

Next.jsのサーバーコンポーネント上で、window.innerWidthなどを使用して画面サイズを取ろうとすると、windowオブジェクトはクライアントサイドでしか動作しないため、エラーとなってしまう。
しかし、文字列が長いときに折り返されるのを避けたいだけであれば、
TailwindCSSのline-clampを使用することで、動的な処理を一切書くことなく解決できる。
https://tailwindcss.com/docs/line-clamp

<p className='line-clamp-1'>Lorem ipsum dolor sit amet,</p>

と書くだけで、画面要素の幅より長い文字列は、その要素幅にあった長さに丸められ、末尾に「...」が付与される。
もちろん、line-clamp-3と書けば3行の範囲内で丸められ、ブログ記事の冒頭をチラ見せするのに使用するなど、複数行表示するのに使う方が多いかもしれない。

morit4ryomorit4ryo

縦方向に数行要素を並べた上で、更に横スクロールする時のTailwindCSSのクラス

<ul className='flex flex-col overflow-x-auto flex-wrap max-w-430px h-[390px]'>
  {items.map((item: any, index: number) => (
    <li key={index} className={index < 5 ? 'mx-5' : 'mr-5'}>
      <div className={w-[calc(100vw_-_40px)] max-w-[370px] h-[70px]}>省略</div>
      <div
        className={`w-[calc(100vw_-_40px)] max-w-[370px] h-1px bg-gray-300 mb-[9px] ${
        index % 5 === 4 || index === items.length - 1 ? 'hidden' : ''
        }`}
      />
    </li>
  ))}
</ul>

ポイント

  • <ul>内のTailwindCSSクラス
    • flex-colで縦方向に並べる
    • overflow-x-autoで横方向にスクロール
    • flex-wrap h-[390px] で、390pxを超えたら折り返して並べるようにしている(390pxはliを5つ並べた時の高さが80px * 4 + 70px = 390pxのため)
    • max-w-430px で表示幅をトリミング
  • <li>内のTailwindCSSクラス
    • items.lengthを活用し、状況に応じたクラスの使い分け
      • コード3行目:最初の5つの<li>だけは左右にマージンをつけたい、それ以外は右側にだけつけたい
      • 7行目:縦に<li>を5行ならべた際の5つ目と最後の<li>にはこの要素を表示させない
morit4ryomorit4ryo

Next.js 14でのfavicon設定方法

https://nextjs.org/docs/app/api-reference/file-conventions/metadata/app-icons
古いVer.のNext.jsでの記事では他の画像と同じように、publicに配置して自力で<head/>に記述するものが多いが、上記リンク先で書かれている通り、faviconapple-touch-icon/appの中に置けばよい
しかしandroid-chromeについては記載がない

android-chromeに関する仮説】

  1. Next.jsでプロジェクトを作成したときに入っているデフォルトのfavicon.icoには256 * 256も含まれていたので、favicon.icochrome-androidもカバーしている?
  2. icon.pngとして256 * 256のiconを作成すればそれが適用される?

仮説を元に必要なファイル・画像サイズを洗い出す

種類 ファイル名 サイズ
favicon favicon.ico 16x16, 32x32, 256x256
apple-touch-icon apple-icon.png 180x180
chrome-android icon.png 256x256

検証結果

https://libre-co.com/markup/favicon-howto/#sp(androidios)
を参考に、
AndroidのChromeでページのタブアイコン、お気に入り登録した際のアイコン、ホーム画面に追加した際のアイコンを確認した結果、

  • タブ ... favicon.icoが出たりicon.pngが出たりと結果がまちまち(同サイズだからか?)
  • お気に入り ... icon.pngが表示される
  • ホーム画面 ... アイコン表示されず(URLの一文字目+Chromeアイコンに)

と必ずしもicon.pngが表示されるとは限らない結果に。
ただ、他サイトをホーム画面に追加してもアイコンが表示されるケースが確認できなかったため、ホーム画面に関しては別のところに原因がある可能性も。
またChromeで新規タブを開いた際に表示される、クイックアクセスにはicon.pngが表示されることを確認した。

morit4ryomorit4ryo

Next.js 14で動的にOGP画像を生成する際のつまづきポイント

https://nextjs.org/docs/app/api-reference/file-conventions/metadata/opengraph-image
基本はこれに沿って行う

export const runtime = 'edge'

runtimeのデフォルトはnextjsなので要注意


つまづきポイント0:プレビューしたい…!

https://og-playground.vercel.app/
でできる!


つまづきポイント1:画像は<img><div>で読み込む

いつもの習慣でimport Image from 'next/image'しようとしてしまったが、
(正確にはfunction Image()とぶつかるため、import NextImage from 'next/image'

Error: failed to pipe response
Cannot access Image.toString on the server. You cannot dot into a client module from a server component. You can only pass the imported name through.

というエラーになる
やりたいことは画像を読み込むだけなので、今回は<img>タグで回避

つまづきポイント1.5:<div>のdisplayはflexnoneしか選べない

些細なことではあるが、意外とこれに触れている記事は少ない
flexnoneしか選べない時点で、必然的に<div style={{display: 'flex'}}>となる
詳細はこちら
https://github.com/vercel/satori?tab=readme-ov-file#css


つまづきポイント2:日本語フォントの読み込み

https://zenn.dev/temasaguru/articles/2968736b5a2f41#フォントファイルの読み込みについて
こちらを参照して解決


つまづきポイント3:TailwindCSSで書ける、ただしtailwind.config.tsは効かないし、いくつか効かないクラスがある

https://vercel.com/blog/introducing-vercel-og-image-generation-fast-dynamic-social-card-images
実際に確認できたものとしては、truncateline-clamp-1object-containあたりが効かず、styleにcssで記載した
しかし、textOverflow: 'ellipsis'は適切に効かず、...が途切れてしまうことがあった

※また<h1>などにはデフォルトで上下にmarginが入っているので、m-0などが必要


つまづきポイント4:ドメインの取得

https://qiita.com/P-man_Brown/items/3d0e0ad09db568848367
上記を参照してapp/middleware.tsを作成し、ドメインを取得したい箇所(今回はopengraph-image.tsx)に下記の要領で記載
(※上記例ではlayout.tsxに記載しているが、layout.tsxに書かないといけないわけではない)

app/***/[slug]/opengraph-image.tsx
/* eslint-disable @next/next/no-img-element */
import { ImageResponse } from 'next/og'
import { headers } from 'next/headers'

// (中略)

export default async function Image({ params }: { params: { slug: string } }) {
  const header_url = headers().get('x-url') || ''
  const url = new URL(header_url)
  const hostName = url.host // ドメイン名(ホスト名)を取得
  const protocol = url.protocol // プロトコルを取得 ('https:' または 'http:')
  return new ImageResponse(
    (
      <div>
        <img
          src={`${protocol}//${hostName}/ogp.png`}
          alt={''}
        />
      </div>
    )
  )
}
morit4ryomorit4ryo

縦横スクロールのスタイル調整(特に横)

横スクロールがあるデザインを行う時、ブラウザごとの横スクロールバーデザインの違い・OSによる表示/非表示の設定(Mac)によってレイアウトが崩れてしまうことがある
こちらの解決はTailwindCSSでは不可能

globals.css
::-webkit-scrollbar {
  width: 10px;
  /* ↓ 横スクロールバー用にheightが必要 */
  height: 10px;
  background-color: #f7f7f7;
}

::-webkit-scrollbar-thumb {
  background-color: #c9c9c9;
  /* ↓ あまり理想的な形状にはならないため、思い切って省くのもアリでは */
   border-radius: 8px;
}

/* 細かく指定するのは面倒なのでhtmlに直で適用する(bodyだと適用できない)*/
html {
  scrollbar-color: #c9c9c9 #f7f7f7;
  /* ↓ 横スクロールバーにも適用される様子 */
  scrollbar-width: auto;
}
morit4ryomorit4ryo

画像の保存防止(Next.js)

https://kakechimaru.com/img_dl_ban/
https://office-obata.com/report/memorandum/post-6035/
これら2つを掛け合わせて、右クリック・ドラッグ&ドロップによる画像保存を行う方法として、以下を採用した

globals.css
/* 画像保存防止 */
img {
  /* PCの右クリック禁止 */
  pointer-events: none;
  /* SPの長押し禁止 */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -moz-touch-callout: none;
  -moz-user-select: none;
  user-select: none;
}

なお、layout.tsx<body />next/imageImageoncontextmenuonselectstartonmousedownの処理を書こうとしたが、エラーとなってしまった(client componentではないことによるものと、next/imageの仕様によるもの)ため、画像とその外側のdivを別途client componentとして書き出し、そちらに以下のように記載した

<div
    onContextMenu={(e) => e.preventDefault()}
    onSelect={(e) => e.preventDefault()}
    onMouseDown={(e) => e.preventDefault()}
>
    <Image />
</div>
morit4ryomorit4ryo

よさげなIdenticon3選

マイページなどにユーザーアイコンを設定したい、しかしユーザ情報はユーザ本人にしか表示されないので、ユーザが任意に画像を設定できる仕組みまで作るのはオーバーヘッド→Identiconライブラリを使おう!
【GitHub風Identicon】minidenticons
https://github.com/laurentpayot/minidenticons

npm install minidenticons

【幾何学だけどかわいい】JDENTICON
https://jdenticon.com/

npm install jdenticon

【ゆる顔アイコン・マーブル模様など6パターン】
https://github.com/boringdesigners/boring-avatars

npm install boring-avatars

こちらで試せる
https://boringavatars.com/516d7d-2a728e-9d870c-f93f03-f9eee2

minidenticonsとboring-avatarsを試したが、boring-avatarsの方が色味を設定できたり、useMemoが必要ないため、server componentでも使用できるなど、使い勝手は良い
ただ、6種類の見た目があるが、実際にアイコンとして特徴的な見た目が生成されるのは、beamとbauhausの2種類と感じた

morit4ryomorit4ryo

popover APIを使おうとして断念した話:

  • tailwindCSSはv4.0betaにてpopoverに対応していたが、アニメーション(opacityの変化)が制御できなかった
    • 書き方の問題はありそう
    • 階層が複雑なものだと、祖先クラスを見なければいけなかったり、クラス表記が汚くなっていく気配
    • そもそもbetaなのでもう少し待っても良さそう
  • そもそもモバイルファーストサイトのヘッダーメニューモーダルに使用しようとしたが、相性が悪い
    • 全ての要素の上に表示されるので、元々はヘッダーとメインの間にモーダルを挟み込んでいたが、モーダルがヘッダーの上に来てしまい、ヘッダーを閉じるボタンも見えなくなってしまった
      • モーダル内に閉じるボタンを書くことはもちろんできるが、冗長に思った
    • 元々はjsを使ってリンク・ボタンがない領域をクリックしたらモーダルが閉じるようにしていたが、popoverでは「モーダル外」を押さないと閉じないため、モバイルでの全画面モーダルと相性が悪すぎた

popoverAPIが悪いという話ではなく、適切なシーンではなかった、という話

popoverはこれが分かりやすい
https://qiita.com/ksk1015/items/d2b1dd2702a22045fba2
TailwindCSSでの基本的な書き方
https://tailwindcss.com/docs/v4-beta#popover-open-support

morit4ryomorit4ryo

複数の画像が横スクロールし続ける、をTailwindCSSでやる:
(アクセシビリティ的には一時停止機能の追加がしたいところ)
(都合により没にしたので供養)
例はコーポレートサイトなどで良くみる、実績事例などで表示されている取引先企業ロゴが複数流れているイメージ
アニメーションのところは長くなっちゃうので別途CSSにまとめている
同じようなことを2回書いているのは、こうすることで途切れずにループするから

index.html
<div class="flex overflow-hidden w-full">
      <ul class="flex flex-none list-none p-0 animate-infinity-scroll-left-1">
            <li class="flex-none w-24 h-12 mr-2 bg-gray-500">企業ロゴ1</li>
            <li class="flex-none w-24 h-12 mr-2 bg-gray-500">企業ロゴ2</li>
            <li class="flex-none w-24 h-12 mr-2 bg-gray-500">企業ロゴ3</li>
            <li class="flex-none w-24 h-12 mr-2 bg-gray-500">企業ロゴ4</li>
      </ul>
      <ul class="flex flex-none list-none p-0 animate-infinity-scroll-left-2">
            <li class="flex-none w-24 h-12 mr-2 bg-gray-500">企業ロゴ1</li>
            <li class="flex-none w-24 h-12 mr-2 bg-gray-500">企業ロゴ2</li>
            <li class="flex-none w-24 h-12 mr-2 bg-gray-500">企業ロゴ3</li>
            <li class="flex-none w-24 h-12 mr-2 bg-gray-500">企業ロゴ4</li>
      </ul>
</div>
@keyframes infinity-scroll-left-1 {
  0% {
    transform: translateX(100%);
  }
  to {
    transform: translateX(-100%);
  }
}
@keyframes infinity-scroll-left-2 {
  0% {
    transform: translateX(0);
  }
  to {
    transform: translateX(-200%);
  }
}
.animate-infinity-scroll-left-1 {
  animation: infinity-scroll-left-1 50s -25s linear infinite;
}

.animate-infinity-scroll-left-2 {
  animation: infinity-scroll-left-2 50s linear infinite;
}
morit4ryomorit4ryo

CSSだけでフッターをページ下部に張り付かせる方法:
別にjsとかを使わずとも、flexの応用で可能だった
https://medium.com/@bbxxuw/コンテンツの高さが足りない時もフッターを画面最下部に固定表示させる-45f1a67966f7
TailwindCSSで書くとこうなる:

<body class="h-full flex flex-col">
  <header><h1>logo</h1></header>
  <main class="flex-grow shrink-0 basis-auto">main</main>
  <footer>footer</footer>
</body>

(元ページではhtmlにもheight: 100%つまりh-fullを適用していたが、これはむしろ適用するとhtml全体の高さが画面の高さで固定されてしまうので不要と思われる)

ちなみに、モバイルファーストのように<body>直下に<body>的な機能を果たす囲いを作る場合、以下のようなコードで動作することを確認した

<body class="h-full"> ここから
    <div class="flex flex-col max-w-md min-h-dvh mx-auto"> ここにを移し、min-h-dvhを足す
      <header><h1>logo</h1></header>
      <main class="flex-grow shrink-0 basis-auto">main</main>
      <footer>footer</footer>
    </div>
</body>

<body>にあったflex flex-colを1行下の<div>に移し、さらにmin-h-dvhを指定することで、コンテンツが少なくても100dvhの高さを保証している