Next.js(App Router)でBASIC認証をかける

2025/01/09 (木) - 09:00 JavaScriptServer

Next.jsのApp RouterでBASIC認証(基本認証)をかける場合は、middlewareという機能を使用します。これを使うとクライアントとサーバーの中間の処理制御を行えます。今回は以下の環境にて実装テスト。

  • Next.js v14.xx

middlewareを使用するにはmiddleware.tsというファイルを作成し処理を記述します。このmiddlewareはクライアントからのリクエストに対し様々なレスポンス処理をするものです。例えばアクセス認証やリダイレクトなどを行うことができ、nginx.confや.htaccessみたいな事ができます。ただしNext.jsでは現時点でmiddlewareを1つしか配置できませんのでsrcディレクトリがあれば、src/middleware.tsに配置するようにします。

middleware.tsでBASIC認証をかける

middleware.tsに以下のように記述しました。

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

const username = process.env.BASIC_AUTH_USER;
const password = process.env.BASIC_AUTH_PASS;

export function middleware(request: NextRequest) {
 const authHeader = request.headers.get('authorization');
 if (authHeader) {
  const encodedCredentials = authHeader.split(' ')[1];
  const decodedCredentials = atob(encodedCredentials);
  const [reqUsername, reqPassword] = decodedCredentials.split(':');
  if (reqUsername === username && reqPassword === password) {
   return NextResponse.next();
  }
 }
 const response = new NextResponse('Authentication required', { status: 401 });
 response.headers.set('WWW-Authenticate', 'Basic realm="Secure Area"');
 return response;
}

export const config = {
 matcher: '/((?!api|_next/static|favicon.ico|sitemap.xml|robots.txt).*)'
};

config.matcherにマッチングルールを指定します。上記の例だとfavicon.icoや画像などの静的ファイルは認証から除外します。

そして、認証に必要なユーザ名とパスワードは安全のため.envファイルに記述します。

BASIC_AUTH_USER=user
BASIC_AUTH_PASS=password

認証がうまく行ったらアクセスしたコンテンツを表示し、認証に失敗したら401ステータスを返します。設定後Next.jsを再起動すると動くと思います。

メディアクエリとコンテナクエリの使い分け

2025/01/06 (月) - 09:00 CSS

レスポンシブデザインで必須となるメディアクエリ(Media Query)とコンテナクエリ(Container Query)。どちらを採用すべきなのか迷うことがありましたので、目的や使用シーンを整理しました。

メディアクエリ(Media Query)とは?

メディアクエリは、主にビューポート(viewport)のサイズに基づいてスタイルを変更するために使用されます。デバイスの画面幅や高さに応じて、異なるCSSスタイルを適用することができます。

ブレークポイントを設定することで、ブラウザの画面幅のピクセル数や、デバイスの向き(縦向き、横向き)などでCSSスタイルを切り替えることが出来ます。

@media screen and (max-width: 768px) {
 body {
  color: red;
 }
}

上記では、ブラウザの表示領域が768px以下だったら、文字色が赤くなります。

コンテナクエリ(Container Query) とは?

コンテナクエリは、親要素(コンテナ)のサイズに基づいてスタイルを変更するための技術です。特定の要素がどのようなサイズに収まるかによって、子要素のスタイルを動的に変更することができます。

<div class="parent">
 <p class="child">子供要素</p>
</div>

コンテナクエリを実現するには必ず親となる要素と、スタイルを適用する子要素が必要です。親要素のセレクタにはcontainer-typeプロパティを指定し、値にはinline-sizesizeのいずれかの値を指定をします。

  • normal : デフォルト値(解除)
  • inline-size : インライン軸(横幅)
  • size : インライン軸とブロック軸(縦横幅)
.parent {
 container-type: inline-size;
}
@container (max-width: 480px) {
 .child {
  color: red;
 }
}

子要素のセレクタには幅指定とスタイルを指定します。上記のコードでは親要素の幅が480px以下の場合、子要素の文字色が赤くなります。

コンテナクエリとメディアクエリの使いどころ

メディアクエリ

ページ全体のレイアウト制御に使うことが多いです。例えば画面幅によってがらっとレイアウトが変わるような制御や、スマホやタブレットの向きでレイアウトを変更する場合は、メディアクエリが最適です。

コンテナクエリ

特定の要素のサイズ変更に応じて細かいデザイン制御したい場合は、コンテナクエリが最適です。例えばナビゲーションやフォームなど、デザインパーツやコンポーネント単位で細かいデザインを制御したい要望ではコンテナクエリが最適です。

コンテナクエリのブラウザの対応状況は、Chromeは105以降、Safariは16以降、Firefoxは110以降で利用でき2025年1月現在の最新ブラウザでは利用することが可能ですので、今後積極的に使うケースは多くなると思います。

Astro+WordPress 記事検索ページをSSRで作る時のポイント

2024/12/27 (金) - 09:00 JavaScriptProgram

2024年最後の更新かな?

AstroとWordPressを組み合わせてヘッドレスCMSで検索ページを作った際につまづいた点。Next,jsとは異なりAstroは基本SSGで静的書き出しをすることを前提としたシステムで、SSRでサーバーサイド処理する場合は少し設定が必要です。要は公式ドキュメントに書いてあるのでよく読もうね〜というお話です。今回の開発環境は以下の通り。

  • WordPress 6.5.x (PHP 8.2.x)
  • Astro 4.16.x

まず、コンフィグファイルのastro.config.mjsにoutput: 'hybrid'を記述します。

export default defineConfig({
 output: 'hybrid',
 site: 'https://example.com'
})

次にSSRで動作させたい検索結果ページ(/search/index.astro)に以下のように記述します。コード内にexport const prerender = false;を記載することでサーバーレンダリングされるようになります。逆に何も記述していない記事一覧や記事詳細などは静的レンダリングされます。以下は検索フォームからGETパラメータで[s]という値を受け取ってREST APIで検索した例です。

---
export const prerender = false; /* ←これを付加する */
import Layout from '../../layouts/Layout.astro';
import { format } from 'date-fns';
const seaechText = Astro.url.searchParams.get('s');
const JSON_URL = `${import.meta.env.WP_JSON_URL}?_embed&search=${seaechText}`;
const posts = await getPostSearch();
const seaechTotal = await getPostSearchTotal();
async function getPostSearch() {
 const res = await fetch(JSON_URL);
 return res.json()
}
async function getPostSearchTotal() {
 const count = await fetch(JSON_URL).
 then((res) => {return parseInt(res.headers.get('X-WP-Total'))});
 return count;
}
---
<Layout title=`記事検索:${seaechText}件`>
<main>
<h1>{seaechText}の検索結果{seaechTotal}件 見つかりました</h1>
<ul>
 { posts.map((post) =>
  <li>
   <a href={`/${post.slug}`}>
    <p>{post.title.rendered}</p>
    〜〜中略〜〜
   </a>
  </li>
 )}
</ul>
</main>
</Layout>

検索結果の例

また、Astroでサーバーサイドレンダリングで実行するときは、アダプターと呼ばれる追加ランタイムをインストールする必要があります。astro addコマンドでインストールが可能で、例えばサーバがnode.jsであれば以下のコマンドで実行できます。

$ npx astro add node

参考リンク:@astrojs/node | Docs

ChatGPTとGASを連携しスプレッドシートのセルの文章を解析・要約する

2024/12/24 (火) - 09:00 JavaScript

顧客からの問い合わせやレビューなどの文章をAIを使いざっくりと解析と要約をして効率化する方法。GoogleスプレッドシートのGoogle Apps Script(GAS)と、ChatGPT APIを連携することで半自動化をすることが出来ます。実装する前に予めOpenAI developer platformからAPI Keysを取得しておきましょう。

前回作ったメールフォームから送信された内容をGoogleスプレッドシートに集約している場合などにも効果的です。

ChatGPTが要約した内容を実際に表示させた例

GASにスクリプトを実装する

予めスプレッドシートを用意して開いておき、メニューから[拡張機能]→[Apps Script]を選択します。コード入力欄が表示されるので以下のようにコードを入力します。

function cellSummary(targetCell) {
 const apiUrl = 'https://api.openai.com/v1/chat/completions';
 const apiKey = '************'; // ここに取得したAPIキーを入力
 const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
 const promptText = targetCell;
 if (!promptText) {
  return '入力されていません。';
 }
 const payload = {
  model: 'gpt-4-turbo',
  messages: [
   { role: 'system', content: '以下のテキストを分析し、100文字程度で簡潔に要約し、[満足][不満]で分類してください' },
   { role: 'user', content: promptText }
  ],
  max_tokens: 1000
 };
 const options = {
  method: 'post',
  headers: {
   'Authorization': `Bearer ${apiKey}`,
   'Content-Type': 'application/json'
  },
  payload: JSON.stringify(payload)
 };
 try {
  const response = UrlFetchApp.fetch(apiUrl, options);
  const json = JSON.parse(response.getContentText());
  const result = json.choices[0].message.content.trim();
  return result; // 要約結果をスプレッドシートに表示
 } catch (error) {
  return `エラーが発生しました:${error.message}`; // エラー時はエラー文をスプレッドシートに表示
 }
}

スクリプトに構文エラーがないことを確認したら、保存ボタンをクリックして保存します。この時実行権限とスプレッドシートへのアクセス許可、および、外部ネットワークへの連携をしていいか聞いてくるのですべて許可しておきます。

スプレッドシートに数式を入力する

セルに数式を記入する

次に用意されたスプレッドシートで解析内容を表示したいセルにカスタム関数を入力します。このとき引数ととして[解析したいセル番号]を指定します。例えば、解析したい文章がF6セルだったら以下のようになります。

=cellSummary(F6)

入力すると通信が始まり、GPTが解析してくれたようやく内容がそのセルに表示されます。以上、ざっくりとしたChatGPT APIとGoogleスプレッドシート(GAS)の連携方法でした。

transitionでheight:autoを動かす2つの方法。

2024/12/20 (金) - 09:00 CSSJavaScript

アコーディオンメニューをCSSで実装する際、height:0からheight:autoにすることで開閉はしますがトランジションは動きません。それを解決するためにmax-heightを使うテクニックやjQueryのslideToggleを使うことが多かったと思いますが、どうもしっくりこない。今回はそれを暫定的に解決する方法を見つけたので紹介します。

HTMLは以下。dtをタップしたら閉じているdd要素を開閉するようにします。

<dl>
<dt>レシピを見る</dt>
<dd><ul>
<li>パスタ:200グラム</li>
<li>キャベツ:1〜2枚</li>
<li>...</li>
<li>にんにく:1欠</li>
<li>オリーブオイル:大さじ2</li>
<li>塩コショウ:適量</li>
</ul></dd>
</dl>

JavaScriptは以下のように、dt要素をタップした時dt要素自体にclass属性を付けて見た目の動きはCSSで処理するようにします。

const toggleBtn = document.getElementsByTagName('dt');
for (let dt of toggleBtn) {
 dt.addEventListener('click',function(e){
  this.classList.toggle('toggleOn');
  e.preventDefault();
 },false);
}

こちらではdtをタップしたら自分自身に、classList.toggletoggleOnというclass属性値を追加・削除します。

1.gridを使うパターン

少しハックっぽいですが、gridで行の高さを指定するgrid-template-rowsプロパティで指定する方法です。最初は0frで隠しておき、開くときはで1frに切り替えるというものです。実はtransitionが有効なんですね。こちらはgridが使えるブラウザであれば動きます。

dt{
 cursor: pointer;
}
dd{
 display: grid;
 grid-template-rows: 0fr;
 transition: grid-template-rows 0.25s;
}
.toggleOn+dd {
  grid-template-rows: 1fr;
}
ul {
 overflow-y: hidden;
}

2.interpolate-sizeを使うパターン

dd要素にinterpolate-size: allow-keywords;というCSSプロパティを付与し、height: 0;overflow: hidden;で隠しておきます。dtをタップしたらddにheight: auto;の値を付与するとtransitionautoの高さまでトランジションが動きます。このinterpolate-size: allow-keywords;を指定すると、auto/min-content/max-contentといったキーワード系の値に対しても数値が有効になりトランジションが動作します。

dt{
 cursor: pointer;
}
dd{
 interpolate-size: allow-keywords;
 height: 0;
 transition: height 0.25s;
 overflow: hidden;
}
.toggleOn+dd {
  height: auto;
}

非常にシンプルでわかりやすいと思いますが、2024年12月時点ではChrome 129および、Android Chrome 131以降のみ対応しており、SafariやFirefoxでは未対応です。未対応ブラウザではトランジションしないだけでプログレッシブエンハンスメントとして動きはするので問題はありませんが、すべてのブラウザでトランジションさせたい場合はgridを採用するか従来通りmax-heightのテクを採用するのが良さそうです。

参考ページ

WordPressのショートコードで他の記事情報を表示する

2024/12/18 (水) - 09:00 Program

こことは別に運用してるブログのWordPressの記事の中に、引用や関連リンクとして他の記事の情報を貼りたくなりました。単純にエディタのリンク機能から記事の内部リンクを指定して貼っても良いのですが、アイキャッチやタイトルなどの細かい情報をいちいちコピペするのが面倒なので、ショートコードを使い動的に取得出来るように自前で実装しました。

WordPressプラグインでも似たようなことを再現できるのもあると思うのですが、探すのが面倒だったのとデザインカスタマイズが難しかったりするので、独自実装する事にしました。

環境は以下の通り。

  • WordPress 6.5.x
  • PHP 8.1.x

記事IDを指定したショートコードをエディタ内に埋め込む

まずショートコードを作り記事内の任意の場所に埋め込みます。idの属性値には引用したい記事の記事ID(PostID)を指定します。

[relatedLink id="256"]

次にfunction.phpに以下のようにショートコードの表示プログラムを実装します。マークアップや呼び出す関数は表示したい内容やデザインに応じてカスタマイズします。

function relatedLinkFunc ( $attr ) {
 extract( shortcode_atts( array(
  'id' => ''
 ), $attr ));
 $link = get_permalink($id);
 $title = get_the_title($id);
 $date = get_the_time('Y年m月d日',$id);
 $thumbnail = '';
 if(has_post_thumbnail($id)){
  $thumbnailURL  = get_the_post_thumbnail_url($id,'full');
  $thumbnail = "<img src=\"{$thumbnailURL}\" alt=\"\" width=\"400\" height=\"400\"  loading=\"lazy\" />";
 }
 $result = "<div><a href=\"{$link}\">{$thumbnail}<span>{$title}</span><time>{$date}</time></p></a></div>";
 return $result;
}
add_shortcode('relatedLink', 'relatedLinkFunc');

その後、プレビューして問題なく記事が表示されていることを確認し、CSSで見た目をカスタマイズすればOKです。

記事引用の表示例

これにより引用先のタイトルや内容が変わっても自動で反映されるので、運営の手間が省けます。またブログ内の回遊率も良くなるので、PVのアップやSEOの底上げにもつながります。

ViteでReactの開発環境を作る

2024/12/11 (水) - 09:00 JavaScript

新しいReactではcreate-react-appによる新規プロジェクト作成は非推奨になり、他のフロントエンド開発フレームワークの利用を推しています。今回久々にReactの触る機会があったのでViteを使ってみました。Viteはフロントエンドビルドツールで「ヴィート」と呼称します。

Viteの導入

Node.jsが導入されている前提で以下の環境にて構築。

  • Node.js v22.x.x

まずターミナルで構築するディレクトリに移動し、以下のコマンドをタイプ。

$ npm init vite@latest

プロジェクトの名前を聞かれるので、適切な名前を入力。

? Project name: › omikuji

利用するフレームワーク(Vanilla,Vue,React…)を聞かれるので上下カーソルで選択しReactを選択。さらにJavaScriptかTypeScriptかを聞かれるので同様に選択。

? Select a framework: › - Use arrow-keys. Return to submit.
Need to install the following packages:
create-vite@6.0.1
Ok to proceed? (y) y
✔ Project name: … omikuji
✔ Select a framework: › React
✔ Select a variant: › TypeScript + SWC

最後に構築手順が表示されるので、その案内通りにコマンドを実行します。

Done. Now run:

 cd omikuji
 npm install
 npm run dev

まずプロジェクトのディレクトリをカレントに。

$ cd omikuji

デフォルトの状態だとパッケージモジュールがインストールされないのでパッケージをインストール。

$ npm install

終わったら開発環境を立ち上げます。

$ npm run dev
> omikuji@0.0.0 dev
> vite

 VITE v6.0.2  ready in 148 ms
 ➜  Local:   http://localhost:5173/
 ➜  Network: use --host to expose
 ➜  press h + enter to show help

表示されたURLにブラウザでアクセスします。

開発環境を立ち上げた後の図

これで終わりです。簡単ですね。

スクリプトコードを触ってみる

./src/App.jsx を適当に編集します。ディレクトリ構成やファイルの構成や中身はほぼ普通のCreateReactAppで作った時と同じっぽいです。適当におみくじを作成しました。

import { useState } from 'react'
import './App.css'
function App() {
 const [flg, setFlg] = useState(false);
 const [omikuji, setOmikuji] = useState();
 const Omikuji = () => {
  const result = ['大吉','吉','中吉','小吉','末吉','凶','大凶'];
  setOmikuji(result[Math.floor( Math.random() * result.length)]);
  setFlg(true);
 }
 return (
  <div>
   <h1>おみくじにゃ🐱</h1>
   <p><button disabled={flg} onClick={Omikuji}>引くにゃ</button></p>
   <p>{omikuji}</p>
  </div>
 )
}
export default App

テストで作ったおみくじ

npm run buildのコマンドでビルドできます。distというディレクトリが生成されます。

$ npm run build
> omikuji@0.0.0 build
> vite build
vite v6.0.2 building for production...
✓ 32 modules transformed.
dist/index.html                   0.48 kB │ gzip:  0.34 kB
dist/assets/index-****.css    0.75 kB │ gzip:  0.39 kB
dist/assets/index-****.js   145.24 kB │ gzip: 47.18 kB
✓ built in 209ms

Viteは開発環境を即時起ち上げることができ、尚且つビルドや実行速度も速く、パフォーマンスも向上しており、開発効率が飛躍的に向上します。複雑だった設定もシンプルになっているので、次回からはViteを勉強しつつこれで開発をしていこうかなと考えたりしています。

JavaScriptで数字を万,億,兆などの日本語の数単位に変換する

2024/12/06 (金) - 09:00 JavaScript

5,000,000,000とか大きい数字を扱うとき、ぱっと見でいくつなのかわからないときがあります。500,000と書かれるより、50万と表記した方がわかりやすいです。そこでJavaScriptで数字を日本語の数単位に変換するっぽいやつを実装してみました。

京都駅の物件リスト

JavaScript

const srtTani = ['','万','億','兆','京'];
function strJPInt(num) {
 let strVal = [];
 let total = [];
 strVal = String(num).replace( /(\d)(?=(\d\d\d\d)+(?!\d))/g, '$1,').split(',');
 for (let i=strVal.length ; i>0 ; i--){
  total[i-1] = (`${parseInt(strVal[i-1])}${yenTani[srtTani.length-i]}`);
  if(strVal[i-1]==0){
   total[i-1] = '';
  }
 }
 return String(total.join(''));
}
//実行 引数で整数を指定する
console.log(strJPInt(1000)) //1000
console.log(strJPInt(19800)) //1万9800
console.log(strJPInt(9800314026)) //98億31万4026
console.log(strJPInt(-10598002340356)) //-10兆5980億234万356

実用例。たとえば高額なデータを表示するとき等に。

const dataProperty = [
 {
  name: 'こってりラーメン屋',
  price: 50000000
 },
 {
  name: '八つ橋屋',
  price: 50000000
 },
/*中略*/
 {
  name: 'ゲーム会社',
  price: 1000000000000
 },
 {
  name: '半導体会社',
  price: 1500000000000
 }
];
dataProperty.map((data) => {
 console.log(data.name,`${strJPInt(data.price)}円`);
});

実行結果

こってりラーメン屋 5000万円
八つ橋屋 5000万円
(中略)
ゲーム会社 1兆円
半導体会社 1兆5000億円

5,000兆円欲しい!

microCMS&Astroで記事のページネーションを実装

2024/12/02 (月) - 09:00 JavaScriptProgram

ウェブサイトを作る際にお知らせ一覧ページなどで記事を10件ごと表示してページを分割することはよくあると思います。AstroとmicroCMSでは最初からページ分割に対応した機能が用意されていたので、今回はそれを参考に作りました。以下は公式ドキュメントです。

環境は以下の通りで、microcms-js-sdkを利用。

  • microcms-js-sdk 3.1.x
  • Astro 4.16.x

ファイル構成。

├── cast
│   ├── [id].astro
│   ├── index.astro
│   └── page
│       └── [page].astro

サンプルとしてカフェのWebサイトのキャスト一覧を表示するサンプルとしました。例ではmicroCMSのAPIのエンドポイントをcastとし、予めAPIとフィールドや投稿を作っておきます。詳しいことはmicroCMSのチュートリアルにて。

記事一覧

記事情報の取得

microcms-js-sdkのgetAllContentsメソッドですべての投稿を取得ができます。それを呼び出す命令をmicrocms.tsに記述しました。例ではgetCastAll()としています。

//型定義(一部略)
export type Cast = {
 id: string;
 publishedAt: string;
 title: string;
 content: string;
 eyecatch: {
  url: string;
 };
};
export type CastResponse = {
 totalCount: number;
 offset: number;
 limit: number;
 contents: Cast[];
};
/*〜〜中略〜〜*/
export const getCastAll = async () => {
 return await client.getAllContents<CastResponse>({ endpoint: 'cast' });
};

記事一覧ページ

ページネーションを行う記事一覧を作成します。例では/page/[page].astroというファイルで作成。pageSizeプロパティで1ページ内に表示する記事の個数を設定できます。例では6件にしています。

ページネーションの部分はコンポーネント化しています。

---
import Layout from '../../layouts/Layout.astro';
import { getCastAll } from '../../library/microcms';
import Pageing from '../../components/Pageing.astro'
interface Props {
 page: {
  data: Cast[];
  start: number;
  end: number;
  size: number;
  total: number;
  currentPage: number;
  lastPage: number;
  url: {
   current: string;
   next: string;
   prev: string;
  };
 };
}
const { page } = Astro.props;
export async function getStaticPaths({ paginate }) {
 const res = await getCastAll();
 return paginate(res, { pageSize: 6 }); //1ページ内の表示件数
};
---
<Layout title=`キャスト一覧 ${page.currentPage}ページ目`>
 <main>
 <h1>キャスト一覧 {page.currentPage}ページ目</h1>
 <ul>
 {
 page.data.map((content) => (
 <li><a href={`/cast/${content.id}`}>
  <img src={content.eyecatch.url} alt={content.title} loading="lazy" width="600" height="400" />
  <div>
  <h2>{content.title}</h2>
 </a></li>
 ))}
 </ul>
 <Pageing page={page} />
 </main>
</Layout>

ページネーション部分のコンポーネント

他のページでも使い回せるようにページネーション部分はコンポーネントに。/components/Pageing.astroというファイルを生成。

---
const { page } = Astro.props;
---
<p>{page.currentPage} / {page.lastPage}</p>
<p>{page.url.prev && (
 <a href={page.url.prev}>前のページへ</a>
)}
{page.url.next && (
 <a href={page.url.next}>次のページへ</a>
)}</p>

[CSS雑談](IE)Internet Explorer 5〜6の頃こんな事あったよね

2024/11/27 (水) - 20:00 CSS

Web 2.0が称されていた2000年代初頭。Web制作界隈でもtable要素を使ったテーブルレイアウトからセマンティックなHTMLとCSSレイアウトへ移行していた時期でもあり、PCのブラウザは群雄割拠の時代でもありました。まだGoogle Chromeもスマートフォンもない頃です。その当時は以下のブラウザが主流でした。

  • Internet Explorer(Windows IE5.0〜6.0 / Mac IE5.x)
  • Mozilla FIrefox
  • Safari
  • Opera

その中でも問題だったのがIE5〜6(Internet Explorer)でした。IEはW3CのWeb仕様に準拠していないうえに、独自実装やバグが多く他のブラウザと表示や挙動の差を埋めるのに苦労したとかしないとか(当時は8割近くがIEユーザだったので無視はできなかった)。今日はそんな思い出話。

ボックスモデルの解釈

ボックスモデルの違い

当時のIEはボックスモデルの解釈がおかしいという不具合が有り、(X)HTMLのドキュメント宣言やxml宣言の有無でレンダリングモードが変わるなどの挙動がありました。今でいうbox-sizingcontent-box or border-boxみたいなものですね。もちろん当時はbox-sizingのような便利なプロパティはまだありません。

モダンブラウザでの解釈(box-sizing:content-box
widthやheightを指定した時、paddingとborderを含めない
IE(後方互換モード)での解釈(box-sizing:border-box
widthやheightを指定した時、paddingとborderを含める

これにより、IEとモダンブラウザで大きさが変わりレイアウト崩れが起きる…なんてことがありました。回避法としてはpaddingやborderを指定する要素にはwidthやheightを指定しないことでした。

floatを使ったレイアウトテクニック

tableでレイアウトしていた頃は、レイアウトカラムをtable要素で組んでいましたが、CSSレイアウトになってからは回り込み目的のfloatプロパティでWebページの大枠のレイアウトを作るようになりました。まだflexやgridなんて無い頃です。一方floatを使うと回り込み解除(clear)する必要があります。回り込み解除のために任意の場所に無駄にHTMLを追加するのはバッドノウハウだったので、親要素で回り込みを解除するclearfixと呼ばれるテクニックが登場します。

HTML

<div class="parent">
 <div class="left">左カラム</div>
 <div class="right">右カラム</div>
</div>

CSS


/*親ボックス要素*/
.parent{
 width: 100%; /*←(1)*/
}
.parent::after{ /*←(2)*/
 display: block;
 clear: both;
 content: "";
}
/* MacIE用 */
/*\*//*/
 display: inline-table; /*←(3)*/
/* */

/*左レイアウト要素*/
.left{
 float: left;
 width: 20%;
}
/*右レイアウト要素*/
.right{
 float: right;
 width: 75%;
}

(1)で親要素にwidthプロパティで何かしらの値(auto以外)を指定することです。WinIE5〜7ではこれだけでfloatを解除できます(※IE5.0を無視していいのであれば、親要素にzoom:1;というプロパティを指定するだけでもいけた)。IEには独自のhasLayoutという概念がありまして、それが影響していました。

(2)次にモダンブラウザ向けに、after疑似要素を使って無理やり回り込みを解除します。IE5〜7では疑似要素に対応していない為使えません。

(3)MacIEでは、(1)(2)の方法が使えないので、ホーリーハック・バックスラッシュハックと呼ばれるCSSハックを使ってMacIEだけにdisplay: inline-table;を読ませてfloatを解除します。

marginが2倍キャンペーン

ブロック要素に、floatと同じ方向に左右marginを指定するとIEだけ値が2倍になってレイアウトが崩れる…というバグがありました。

.left{
 width: 200px;
 float: left;
 margin-left: 10px; /* ←IEだけ20pxになる */
 display: inline;  /* ←これを指定するだけで直る */
}

対象のセレクタにdisplay: inlineを指定するだけで直る上に、他のブラウザでも影響を受けないので重宝していました。

.top{
 margin-bottom: 50px;
}
.bottom{
 margin-top: 100px;
}

また上下に要素があり、それぞれにmarginがある場合本来は相殺されるのが正しいです。上記の例であれば要素間の隙間は100pxになります。しかしIEでは相殺されず150pxになってしまいます。

font-size固定(px/pt)にするとブラウザでフォントサイズ変更できない

IEではフォントサイズを[最小][小][中][大][最大]と変更できました。しかしfont-sizeプロパティでpxなどを指定するとフォントサイズが変更できない仕様でした。ユーザビリティの観点からフォントサイズを変更できるようにすることが要件だったため、emや%で指定することが必須でした(remはまだ無かった)。

親要素で%やemで指定すると、子要素でもフォントサイズを継承してしまうため、一体この要素は何px相当のフォントサイズなのか?一見して分かりづらいので設計は要注意です。

CSS2セレクタや半透明PNGが使えない

Windows版のIE5〜6ではアルファチャンネルを含むフルカラーPNG画像が使えずデザインに影響が出ることがありました。その場合はCSSハックやPolyfillをつかい、無理やり対応させたりそもそも半透明PNGを使わないという選択肢に迫られました。

一方でMacOS版のIE5では、半透明PNGが使えたんですね…。これはWindows版のIEがTridentというブラウザエンジンなのに対し、MacOS版のIE5はTasmanというブラウザエンジンで全く別物だったことに起因します。これに付随してWindows版のIE5〜6ではCSSの「子供セレクタ div>p{}」「隣接セレクタ div+p{}」が使えないのに対し、MacOS版のIE5では使えたという中途半端な状態となっていました。

IEだけレイアウトが崩れて今日も残業

語りたいことはたくさんあるけれど…

IEはCSSに限らずJavaScriptの実装や動作においても仕様が異なっておりエンジニアの人は苦労しました。その実装の差を埋めるためのフレームワーク、jQueryが登場したのはWeb制作の世界を大きく変えました。IE7〜IE11もといMicrosoft Edge(EdgeHTML版)も面倒なCSSやJSのバグが多くて、長い間苦しめられました。

次回、ガラケーHTML制作苦労話 i-mode,EZwebブラウザ編…ッ!(覚えていたら)

ページの先頭へ