KENTEM TechBlog

建設業のDXを実現するKENTEMの技術ブログです。

ハンバーガーメニューの進化を考えてみた

この記事は、 KENTEM TechBlog アドベントカレンダー2025 3日目、12月3日の記事です。

新卒1年目社員の K・K です

この記事では、Web・アプリ制作に欠かせない UI コンポーネントである ハンバーガーメニュー について、その成り立ちから最新の工夫、そして進化のアイデアまでを分かりやすくまとめました。

React Native 初心者が、React Native の勉強の一環として、ハンバーガーメニューの進化を提案します。難しい内容ではないので、気軽に、温かい目で読んでいただけますと幸いです。


そもそもハンバーガーメニューって何?

ハンバーガーメニューを進化させたい!!!!

...ということで、いきなりですがクイズです。
次のうち、ハンバーガーメニューは何番でしょう?





…… 正解は①!

各選択肢の名前は以下の通りとなっています。

  • ハンバーガーメニュー
  • ケバブメニュー
  • ミートボールメニュー

ハンバーガーメニューはその名の通り、上下にバンズ、その間にパティを挟んだ3本の線で構成されたメニューアイコンとなっています。

なんともシンプルなハンバーガーですね。


見た目について再確認できたところで、ハンバーガーメニューを進化させるための情報を求めて、もう少し探っていきましょう!

歴史と普及の理由

ハンバーガーメニューの起源は 1981年
デザイナー Norm Cox 氏 が、Xerox Star(現代のパソコンの基礎となった商用コンピュータ)の UI 用に考案しました。参考 — Wikipedia

わずかな画素でも扱えること、メニューの一覧とひと目で伝わることを意識して作られたそうです。
当初は hamburger menu ではなく triple bar icon と呼ばれていました。

“ハンバーガー”という呼称が広まったのは スマホ普及のころ(2000年代後半〜)。

  • 省スペースで多くのメニューを実現できる
  • シンプルで拡張性の高いデザイン

これらのハンバーガーメニューの強みが、画面が小さく、メニュー配置の自由度が低いスマホとの相性がよく、再注目に至りました。

気がつけば 40年以上の歴史を持つ大ベテラン UI となっています。大先輩ですね。

今や、スマホに限らず多くのサイトで目にする機会が増えたハンバーガーメニューですが、 利用される数だけその進化も存在します。

では、工夫されたハンバーガーメニューについて知り、ハンバーガーメニューの進化のアイデアを発掘するとしましょう。

工夫されたハンバーガーメニュー

ハンバーガー型

個人的に知っていただきたいハンバーガーメニュー第一位はこちら。 題名の通り、ハンバーガーの形を模したハンバーガーメニューです。

ハンバーガーメニューを進化させたい!と思い立った私は真っ先にハンバーガー要素を増やすことを考えました。 しかし!!!!

そうです。当たり前のようにすでに存在していました...

目を引き、印象に残るデザインです。

アニメーション

アニメーションとの掛け合わせは、ハンバーガーメニュー、その強みを生かした、最たる進化ですね。 動きを持たせることで、目につくデザインはかなり多くありました。

特に人気なアニメーションはこちら。

上下のバンズが✕に変化、閉じるボタンの役割も担うデザインが多く見受けられました。

このようなハンバーガーメニュー自体や、展開したメニュー一覧のデザインの工夫は見ていて楽しいものがありますね。ついつい触ってみたくなります。


そんな便利なハンバーガーメニューですが、残念ながら完璧な UI とは言えません。 前述でも述べた通り、多くの人に使われるようになったのは意外にも最近で、誕生からスマホ普及まであまり広まらなかったのにも理由があります。

ハンバーガーメニューの弱みについても理解しましょう。

デメリット

  • 見つけづらい
  • 操作数が増える

1つ目のデメリットは「見つけづらさ」です。 ハンバーガーメニュー自体の認知度は高まっていますが、開いてみるまで中に何があるのかが分からないという点は変わりません。 そのため、本来使ってほしい導線がユーザーに気付かれず、結果的に利用されないケースも起こり得ます。

続いて2つ目のデメリットは「操作数が増える」という点です。 仮に見つけてもらえたとしても、ハンバーガーメニューの中に格納されているため、操作が増えてしまいます。

例えば、本来であれば 1クリックで到達できた機能が、

  1. ハンバーガーメニューを開く
  2. メニューの中から探す
  3. 目的のメニューをタップする

このように 複数ステップを踏む必要が生じてしまい、UX的な負担がどうしても大きくなります。

私は何でもかんでもハンバーガーメニューの中に入れてしまいたくなりますが、どうもそういうわけにはいかなそうです。

いざ、ハンバーガーメニューを進化

ここまででハンバーガーメニューについては詳しくなれたかと思います。 では、実際にハンバーガーメニューを進化させるにあたって、集めてきた情報を整理します。

  1. ハンバーガーメニュー内に含まれているメニューがわかりづらい
  2. 操作数が増える

克服しなければならない点は以上の2点ですが、1については、すでに「見つけやすさ」の改善として、工夫されたハンバーガーメニューで取り上げているため、残る2の改善を考えていきましょう。

この記事の冒頭でお話ししましたが、あくまでReact Native の勉強の一環なので、React Native の強みを生かし、上記のデメリットの解決を行います。

React Native は端末状態の取得が得意。 そこで私が考えたのは、画面の向きに応じて自動でメニューが少し展開される UIです。

  • 縦画面 → 通常のハンバーガーメニュー
  • 横画面 → 一部メニューを露出 + ハンバーガーが崩れるアニメーションで動線を示す

内容としては、スマホを縦画面で操作している最中は通常のハンバーガーメニューですが、横画面にすることでメニューの一部を展開し、アニメーションを用いてそのメニューがハンバーガーメニュー内に含まれていることが理解できるといった内容です。利用率の高いメニューを展開することで、ハンバーガーメニューの機能を損なわず、操作数を減らすことができます。

「横長の画面時にハンバーガーメニューを展開する仕組み」自体はすでにありますが、今回はアニメーションを用いて、その内容が「ハンバーガーメニューの中に格納されていた」ことがわかる点を主軸としています。

では実際に動かしたイメージをご覧ください。

実際に動かしたイメージ

実装コード

コードはこちらをクリック

import React, { useRef, useState, useEffect } from "react";
import { View, Animated, StyleSheet, Text, Pressable, Dimensions } from "react-native";

export default function App() {
  // -----------------------------
  // アニメーション用のAnimated.Value
  // -----------------------------
  const translateY = useRef(new Animated.Value(0)).current; // ハンバーガーバー縦移動
  const translateX = useRef(new Animated.Value(0)).current; // ハンバーガーバー横移動
  const rotateValue = useRef(new Animated.Value(0)).current; // ハンバーガーバー回転
  const menuTranslateX = useRef(new Animated.Value(-100)).current; // メニュー横スライド
  const menuOpacity = useRef(new Animated.Value(0)).current; // メニュー透過

  // -----------------------------
  // 状態管理
  // -----------------------------
  const [isMenuOpen, setIsMenuOpen] = useState(false);   // 横画面時にメニューを開くか
  const [isMenuReady, setIsMenuReady] = useState(false); // メニューがボタンに変化するか

  // -----------------------------
  // 横画面検知&アニメーション実行
  // -----------------------------
  useEffect(() => {
    const subscription = Dimensions.addEventListener("change", ({ window }) => {
      const isLandscape = window.width > window.height;
      setIsMenuOpen(isLandscape);
      setIsMenuReady(false); // 一旦リセット

      Animated.parallel([
        Animated.timing(translateY, {
          toValue: isLandscape ? 10 : 0,
          duration: 600,
          useNativeDriver: true,
        }),
        Animated.timing(translateX, {
          toValue: isLandscape ? 20 : 0,
          duration: 500,
          useNativeDriver: true,
        }),
        Animated.timing(rotateValue, {
          toValue: isLandscape ? 1 : 0,
          duration: 300,
          useNativeDriver: true,
        }),
        Animated.timing(menuTranslateX, {
          toValue: isLandscape ? 0 : -100,
          duration: 500,
          useNativeDriver: true,
        }),
        Animated.timing(menuOpacity, {
          toValue: isLandscape ? 1 : 0,
          duration: 500,
          useNativeDriver: true,
        }),
      ]).start(() => {
        if (isLandscape) setIsMenuReady(true);
      });
    });

    return () => subscription.remove();
  }, []);

  // -----------------------------
  // メニュー項目
  // -----------------------------
  const menuItems = ["メニュー 1", "メニュー 2", "メニュー 3"];

  // -----------------------------
  // Animated 用スタイル
  // -----------------------------
  const animatedBurgerStyle = {
    transform: [
      { translateY: translateY },
      { translateX: translateX },
      { rotate: rotateValue.interpolate({ inputRange: [0, 1], outputRange: ["0deg", "90deg"] }) },
    ],
  };

  const animatedMenuStyle = {
    opacity: menuOpacity,
    transform: [{ translateX: menuTranslateX }],
  };

  return (
    <View style={styles.container}>
      {/* -----------------------------
          ヘッダー
      ----------------------------- */}
      <View style={styles.header}>
        {/* ハンバーガー */}
        <View style={styles.burger}>
          <Animated.View style={[styles.bar, animatedBurgerStyle]} />
          <View style={styles.bar} />
          <View style={styles.bar} />
        </View>

        {/* 横スライドで出現するメニュー */}
        <Animated.View style={[styles.menuWrapper, animatedMenuStyle]}>
          {menuItems.map((title, index) =>
            isMenuReady ? (
              // ボタン状態
              <Pressable key={index} style={styles.menuButton}>
                <Text style={styles.menuText}>{title}</Text>
              </Pressable>
            ) : (
              // バー状態
              <View key={index} style={styles.menuBar} />
            )
          )}
        </Animated.View>
      </View>
    </View>
  );
}

// -----------------------------
// スタイル定義
// -----------------------------
const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: "#fff" },

  // ヘッダー
  header: {
    width: "100%",
    height: 120, 
    backgroundColor: "#eee",
    flexDirection: "row",
    alignItems: "center",
    paddingLeft: 16,
    paddingRight: 10,
  },

  // ハンバーガー
  burger: { gap: 6 },
  bar: {
    width: 30,
    height: 5,
    backgroundColor: "#333",
    borderRadius: 3,
  },

  // メニュー全体
  menuWrapper: {
    flexDirection: "row",
    alignItems: "center",
    marginLeft: 10,
  },

  // スライド中のバー
  menuBar: {
    width: 60,
    height: 8,
    backgroundColor: "#666",
    borderRadius: 4,
    marginLeft: 12,
  },

  // ボタン状態
  menuButton: {
    backgroundColor: "#666",
    paddingVertical: 8,
    paddingHorizontal: 16,
    borderRadius: 8,
    marginLeft: 12,
    alignItems: "center",
    justifyContent: "center",
  },
  menuText: {
    color: "#fff",
    fontSize: 16,
    fontWeight: "bold",
  },
});

まとめ

いかがだったでしょうか。 アニメーションですが、ハンバーガーが崩れるイメージを意識して動かしてみたので、ハンバーガーメニューの名に恥じない進化になったのではないでしょうか?

UI 制作では、つい“いつもの形”を選んでしまいがちです。
ですが、ただの惰性で使い続けるのはもったいない。

適材適所 を意識し、既存 UI の特徴を理解しながら利用していくことで、利用者により良い体験を届けられます。

たまには、”進化させてみよう”なんて考えるのもいいかもしれません。

この記事が、UI を考えるきっかけになれば嬉しいです。

おわりに

KENTEMでは、様々な拠点でエンジニアを大募集しています! 建設×ITにご興味頂いた方は、是非下記のリンクからご応募ください。 recruit.kentem.jp career.kentem.jp