⛰️

筑波大学学園祭 Web サイト構築の舞台裏

2022/12/15に公開

年の瀬ですね。こんにちは、いなにわうどん(@kyoto_mast21)です。
11 月 5, 6 日に、筑波大学では雙峰祭(そうほうさい)と呼ばれる学園祭が開催されました。私は雙峰祭 Web サイトのフロントエンドのデザインや実装を主に担当したので、その工夫や裏話的なものを少しお話しできればと思います。

Web サイトの開発光景
Web サイトの開発光景

本稿は 学園祭非公式 Advent Calendar 2022 の 14 日目の記事です。12 日目の担当は issy さんでした[1]

背景

学園祭の運営を担う筑波大学学園祭実行委員会には、情報メディアシステム局[2](以下、jsys)と呼ばれる部門が存在しています。jsys では、学園祭で使用する映像の制作・生中継[3]等に加え、学園祭運営に必要な各種システムや Web サイトの内製を担当しています。

https://github.com/sohosai

例年、雙峰祭の前後 1 ヶ月程度は、その年のテーマに合わせた特設の Web サイト(以下、年度 Web)が展開され、企画検索やタイムスケジュール等を中心に、様々なコンテンツが掲載されます。


今年度の雙峰祭サイトのスクリーンショット。https://sohosai.com より引用

掲載情報

サイトに掲載する情報は以下の通りです。

  • 雙峰祭に関するご案内
  • 企画検索
    キーワードやジャンル、実施形態で絞り込みができます
  • 学内マップ
  • ステージ企画・生配信
  • そぽたん[5]の紹介、グッズ紹介、学術企画紹介、アクセスマップ……等々

設計とデザイン

今年の年度 Web では、主に以下の 3 点を念頭に置いてデザイン・設計を進めました。Figma 等のデザインツールを利用したプロトタイプ等は特に制作せず、簡単なラフスケッチを基に、あとはコードを書きながらデザインの検討を進めました。個人開発[6]あるあるですね

  • 見ていて楽しい
  • 迷いのないナビゲーション
  • タイポグラフィ・組版

見ていて楽しい

今年度の学園祭は 3 年ぶりの対面開催となったため、動画や写真を全面的に採用して、見ていて楽しく、一度学園祭に足を運んでみたくなるような、賑やかで和気藹々としたデザインを目指しました。

特に、トップページは他のページと比較して多くの PV が想定されるため、注力してデザインを行いました。ヒーロームービーと呼ばれる動画を全面に採用したり、アニメーションを随所に取り入れたりしています。「祭りを盛り上げろ!」のロゴモーションは、別の局の方にデザインしていただいたタイポグラフィに、@hi13i_ くんが AE でモーションを付けてくれたものです。
当日は現在実施されているステージ企画がリアルタイムで表示され、トップページから生配信を見ることも出来ました。

https://twitter.com/kyoto_mast21/status/1588728546150998018

迷いのないナビゲーション

迷いのないナビゲーションを心掛けました。
新型コロナウイルス感染症[7]の影響で、今年の学園祭は事前予約制で行われました。したがって、sohosai.com を閲覧された方が「筑波キャンパスで開催とあるぞ、行ってみよう」と事前予約なしに来場され、当日に現地で事前予約がないために入場をお断りする事態も想定されました。
こうした事態は避けるべきですので、ファーストビューの時点で重要な情報を把握できるように努めました。


大量に出現する事前予約の文字列

組版・タイポグラフィ

Web においてもコンテンツの主体はテキストであるため、美しく文字を組み上げる組版タイポグラフィを重視しました。文字組みの工夫に関しては実装パートで後述します。

今年の雙峰祭のスローガンである Invigorate The Festival! は Futura をベースとしたものであったため、Web サイトでも随所に Futura PT を使用し、また和文書体には筑紫 AM ゴシックを活用しました。また、本文書体としては Brother 1816(欧文)と りょうゴシック PlusN(和文)を採用しました。Adobe Fonts は本当に助かります。


多様なフォントがサイトを彩る

実装

実装に際しては、Next.js[8], styled-components[9] を用いて構築し、Vercel にホスティングしました。企画内容やお知らせ等の変動が見込まれるコンテンツは Headless CMS である StrapiContentful から配信され、ISR(Incremental Static Regeneration)[10]を用いて、数分に一度自動更新が走るようになっています。

このような構成は Jamstack と呼ばれ、近年の Web アーキテクチャでは比較的メジャーな存在です。コンテンツの管理機能とユーザーに対する表示部分(ビュー)を分割して開発し、ビルド時に API を通じてコンテンツを埋め込むことで、静的サイトとしてデプロイが可能となります。また、柔軟なフロントエンド開発や、表示速度の高速化といった効果も期待されます。

sohosai.com のソースコード
minify そぽたん

以下、こだわり実装ポイントや感じた課題について述べていきます。

レスポンシブ対応

レスポンシブデザインとは、画面幅に応じてレイアウトが調節され、PC・スマートフォン・タブレット等いずれのデバイスからの閲覧に対しても最適化されたデザインを指します。
従来(十余年前)であれば PC 端末でのみ美しく表示できればよかったのですが、デバイスの多様化とモバイル端末の普及によって、今日ではレスポンシブ対応は不可欠な存在です。実際、今回のサイトでも 6 割以上のアクセスをモバイル端末が占めていました。

スマートフォンでの表示(手前)と PC での表示(背面)
スマートフォンでの表示(手前)とPCでの表示(背面)

そしてこのレスポンシブ対応、デザインに凝れば凝るほど大変になります
そうした背景もあってか、昨今では本文を中央に配置する 1 カラムレイアウトを採用するサイト[11]が増えています。しかし、折角なら画面をフルに使って表示したいとの思いが強かったため、敢えて時代に逆行した 3 カラムの構成とし、頑張って Media Query をゴリゴリと書く道を選びました[12]。breakpoint[13] は 700px, 1060px, 1200px の 3 つに設定されており、iPhone, iPad, MacBook で表示するとそれぞれに最適化された表示がなされます。

苦労した点の 1 つはグローバルナビゲーションで、ウィンドウの幅に加えて、スクロールの状況に応じてレイアウトが変わる複雑仕様でした。また特定のページ(会場マップ等)に限ってはグローバルナビゲーションを表示しない、といったアドホックな実装も求められたのですが、これは @YuseiIto くんが breakpoint をそのままコンポーネントの props に渡すという画期的な解決策で実装を進めてくれました。感謝!

グローバルナビゲーションの各種デザインが並んでいる図
グローバルナビゲーションの各種デザイン

組版

Typesetting コンポーネントを用意し、組版品質の向上に努めました。対象の文章を本コンポーネントで括ると、それぞれの文字クラス毎に span 要素を付与します。このラベリングを基に、和文/欧文でのフォント・サイズの切り替え、文字間の padding の調節といった処理が行われます[14]。実装としては以下のコードです。

コード
const Wrapper = styled.span`
  font-family: 'ryo-gothic-plusn';
  .latin {
    font-family: 'brother-1816', sans-serif;
    font-weight: ${(props) => (props.font === 'bold' ? 500 : 300)};
    font-size: ${(props) => (props.font === 'round' ? '1.02' : '1.06')}em;
  }
  .others + .latin,
  .latin + .others {
    padding-left: 0.25em;
  }
  // 文字クラス毎の設定が続く
`;

const charClasses: { [key in string]: string[] } = {
  opening: ['「', '『', '(', '[', '〈', '{'],
  closing: ['」', '』', ')', ']', '〉', '}'],
  comma: ['、', '。'],
  space: [' '],
  middleDot: ['・'],
  tab: ['\t'],
};
type charClass = 'latin' | 'opening' | 'closing' | 'comma' | 'space' | 'tab' | 'others' | 'head';
const getCharClass = (char: string): charClass => {
  return char.match(/^[A-Za-z0-9.,!?&@/:\-\(\)\*]*$/)
    ? 'latin'
    : (Object.keys(charClasses).find((key) => charClasses[key].includes(char)) as charClass) ??
        'others';
};

const Typesetting = ({ children }: { chidlren: string }) => {
  const typeset = (line: string, lineIndex: number) => {
    const nodes: ReactNode[] = [];
    let tempChars = '';
    let currentCharClass: charClass = 'head';
    let index = 0;

    const addChars = () => {
      if (tempChars.length > 0) {
        nodes.push(
          <span className={currentCharClass} key={`line${lineIndex}-${index++}`}>
            {tempChars}
          </span>
        );
      }
    };

    for (const char of line) {
      const charClass = getCharClass(char);
      if (['space', 'tab'].includes(charClass)) {
        if (['comma', 'space', 'tab', 'head'].includes(currentCharClass)) {
          continue;
        }
      }
      if (charClass !== currentCharClass || ['opening', 'closing'].includes(charClass)) {
        addChars();
        tempChars = '';
      }
      tempChars += char;
      currentCharClass = charClass;
    }
    addChars();
    return <>{nodes}</>;
  };

  return (
    <Wrapper>
      {children.split('\n').map((line, index) => (
        <React.Fragment key={`line-${index}`}>
          {typeset(line, index)}
          {index < lines.length - 1 && <br key={`break-${index}`} />}
        </React.Fragment>
      ))}
    </Wrapper>
  );
};
export default Typesetting;

下記の画像は、Typesetting コンポーネントを適用した例です。48 の後に四分アキ(1/4 のスペース)が挿入されていたり、 の間のアキが削除されていることがわかります。

雙峰祭サイトをデベロッパツールで開いた画面
雙峰祭サイトをデベロッパツールで開いた画面

地図

@chururi_ 氏の尽力によって学内マップが実装されました!! この実装に関してはちゅるり氏が別記事として執筆してくれたため、是非そちらも併せてご覧ください。
https://qiita.com/chururi/items/93ab81aad98fd8456e15

パフォーマンス

最後まで残存した課題として、サイトのパフォーマンスの低さが挙げられました。Lighthouse の値としては大体 60 程度だったかと思います。
「jsys の人間は強い機材とネットワークを使っているからローディングの重さに気が付かなかった」という訳ではなく、やはり動画が悪い、いや Adobe Fonts の配信が悪いと様々な議論や施策を重ねたのですが、結果として十分なパフォーマンスチューニングには至りませんでした。これはひとえに私の力不足であったと感じています。

Twitter埋め込み

Twitter のタイムラインを Web サイトに埋め込む際は、iframe を介してツイートを表示する widgets.js が公式より提供されています。一方でこのスクリプトは読み込みが遅く、手元の環境では表示に数秒程度を要するなど Performance のスコア低下に直結していました。個別のツイートを埋め込むパッケージは npm で提供されていたのですが、タイムライン自体を埋め込むものは存在しなかったため、SSG 時に API を叩いて静的に生成するように改良を加えました。

実装部屋。雙峰祭カラーと牛丼
お昼ごはんの牛丼

トランスパイラのバグを踏んだ

踏みました。問題となったのは以下のコードです。

[...Array(4)].map(
  (_, index) => <div key={index}>内容</div>
);

ES6 で採用されたスプレット構文と高階関数を採用した何の変哲もないコードですが、このコードを Next 12.2.2 で実行すると、localhost の環境では表示要素が 4 つと想定通りの動作をするものの、Vercel では 3 つしか表示されない、といった不可思議な現象が発生します(厳密には、npm run devnpm run build で挙動が異なる)。

このバグは現役の代では解決に至らなかったのですが、@HosokawaR 氏らを始めとする先代らのご尽力により、Next.js 12 で Babel に代わって標準採用された TypeScript トランスパイラである SWC のバグであったことが判明しました(ありがとうございます)。いや〜マジですか

https://twitter.com/hosokawar_p/status/1588241342659932160?s=46&t=WVTf500GCKihPog8Nud5sA

ビルドが間に合いませんでした!と書かれた Slack メッセージ
不断の努力により、無事に雙峰祭開幕にリリースビルドは間に合いませんでした

むすびにかえて

こうして開発・公開された Web サイトは、学園祭前後の 1 ヶ月間で 25 万を超えるページビューを獲得し、SNS 等でも多くの反応をいただくことができました。ご覧いただき誠にありがとうございました。

https://twitter.com/Futureship1/status/1581635288937680897

反応の多くは肯定的なコメントでしたが、一方で、若干の批判があったのも事実です。アクセシビリティ等の観点も含めて、こうした意見は来年以降の開発に生かしていければと思います[15]

Web サイトは簡素であるべき?

最後に少しだけ、個人的な所感を述べます。SNS で見られた反応の中で印象的だった意見の一つに、「リッチな Web は求めていない、とにかく軽量にしろ」といったものがありました。

Web の世界では、近年の表現力の発展に伴い、そのアンチテーゼとして、複雑な装飾やレイアウトを避けて構築された、簡素な Web を支持する流れが一部で見受けられます[16]
この主張は WWW の起源に鑑みれば正しいかもしれませんが、少なくとも本稿で扱ったような事例に適合するものではないと考えています[17]。学園祭の Web サイトは一過性の制作物に過ぎず、長期的な文書保存や交換を目的としていません。もし学園祭のサイトが 1990 年代の Web 1.0 を彷彿とさせるデザインを採用すれば、そのシンプルさや軽量さを評価する声よりも、「ダサい」などと否定する意見が多く観測されるのではないでしょうか。
そうした点を踏まえると、リッチな Web やインタラクティブな Web を一概に否定する意見には賛同しかねます。もちろん、ユーザ体験を阻害しないような配慮が必要であることは言うまでもありません。

――――
末筆ではございますが、学実委のみなさまを始め、今回の実装にあたってご協力いただいたすべての方々に感謝申し上げます。
そして、雙峰祭 Web に興味が湧いた ITF.23 のそこのあなた!! 学園祭実行委員会情報メディアシステム局(https://sohosai.com)で是非お待ちしています!

関連リンク

脚注
  1. 13 日の執筆者はいませんでした ↩︎

  2. jsysや情シスと呼ばれている ↩︎

  3. 2004年から実施されているらしい。ニコ動より早い[4] ↩︎

  4. 小野永貴, 小西響児: リアルタイムコメントによる視聴者参加型学園祭イベント生中継システムの開発と実践, 映像情報メディア学会年次大会, https://www.jstage.jst.go.jp/article/iteac/2010/0/2010_3-7-1/_pdf ↩︎

  5. 筑波山の妖精。今年はカゴに入れられていた↩︎

  6. 年度 Web の開発は 3 人+ヘルプで行いました。学実委自体は 200 人以上の構成員を有する規模の大きな団体ですが、jsys になると実人数は 20 人以下に、さらにネットワークと映像で半々に割り振るので 10 人にまで減ります。主な開発は 2 年次が行うため、実働のネットワーク要因として割けるのは実質 5 人程度です。今回の学園祭の実施にあたっては、インフラ・Web・映像配信など属人化分担して作業を進めました。 ↩︎

  7. いつまで新型と名乗っているんだ ↩︎

  8. React をベースとする Web アプリケーションフレームワーク ↩︎

  9. CSS in JS には元々抵抗を感じていたのですが、流石に時代に抗えなくなった ↩︎

  10. 基本的には SSG と同様にサーバー側で既にビルドされたコンテンツを返しますが、設定された revalidate(有効期限)を超過した場合は、再度ビルドを実行して最新の状態を保ちます。定期的なコンテンツの更新が見込まれるようなサイトに適しています ↩︎

  11. モバイルファースト(モバイル端末での表示を最優先とする設計)の一環であると思われます ↩︎

  12. 今年の雙峰祭 Web は 1.7 万行のソースコードで構築されました ↩︎

  13. スタイルを明示的に切り替える画面幅の基準点 ↩︎

  14. 合成フォント機能は @font-face ルールと unicode-range を組み合わせることでも実現されますが、サイズの調節をサポートする font-size-adjust プロパティは Firefox 以外のブラウザではサポートは限定的です。また、文字組みアキ量等を調節する text-spacing プロパティに関しては、CSS Text Module Level 4 の仕様として審議中です ↩︎

  15. ただ来年実装を担当するのは私ではないのですが…… ↩︎

  16. 私もシンプルな Web サイトには好感が持てます ↩︎

  17. 例えば技術的文書の公開等を目的とした Web サイトであれば、簡素な Web が望ましいとの主張を適用する余地は十分に存在します。一方で、すべての Web ページの装飾を削ぎ落とせ、といった主張は看過し難いものです。 ↩︎

Discussion