雑多に技術メモと他色々

主に自分用な技術メモが多くなる気がする。他色々が書かれるかどうかは不明。

HTML上の要素表示・非表示をJavascriptだけで切り替える

SNSで流れてきた広告サイトから技術を勉強してみた記録の続き。
前回はこれ。
yamakisso.hatenablog.com

概略

永遠ちゃんねるという過去に存在してたネタページを元に勉強した成果だけ残す。

サンプルコードでページ遷移を再現

実際にサンプルコードを書いて、ページ遷移を再現してみる。
方法が色々思いつくが、模倣じゃなくて参考にして勉強するのが目的なので元ネタから見つかる方法も、自分で思いつく方法も両方確認した。

サンプルの動作期待値

動作の期待値は以下の通りとして、検証する。

  • 入場ボタンが配置された簡易なページを表示しておいてボタンをクリックしたらページが書き換わることを期待する。
  • 実際はページ遷移させずにJavascriptだけで制御する。
遷移前のHTML
<html lang='ja'>
<meta charset="UTF-8"/>
<title>出現/消去のてすとぺーじ</title>
<link rel="stylesheet" type="text/css" href="./common/visible.css"/>
<script type="text/javascript" src="./common/visible.js"></script>
<body>
<!-- DIVタグ:それ自身は特に意味を持たないが、ブロック要素として要素をまとめる場合に使う。 -->
<!-- SPANタグ:DIVとほぼ同様。インライン要素としてまとめる場合に使う。 -->
<div id="top" class="top-page">
  <header>
    <div class="header">TOPページのヘッダです</div>
  </header>
  <div class="logo">ロゴ表示</div>
  <div class="warning"><p>表示非表示を制御するボタンです。</br>気が向いたらクリックしましょう。</p></div>
  <div class="login-line"><div onclick="login()" class="login-button">入場する</div></div>
  <footer>
    <div class="footer">TOPページのフッタです。このHTMLはテストで作りました。</div>
  </footer>
</div>
</body>
</html>
遷移後のHTML

ボタンをクリックした後の状態。
「入場に成功しました!!」の文字列が表示される。

<html lang='ja'>
<meta charset="UTF-8"/>
<title>出現/消去のてすとぺーじ</title>
<link rel="stylesheet" type="text/css" href="./common/visible.css"/>
<script type="text/javascript" src="./common/visible.js"></script>
<body>
<div id="main" class="main">
  <div class="text-main">入場に成功しました!!</div>
</div>
</body>
</html>
サンプルCSS(visible.css)
@charset "UTF-8";
/* Topページ */
div.top-page {
  background-color: #EEFFFF;
}
/* ヘッダ */
div.header {
  width: 100%;
  height: 50px;
  margin: 0 0 10px;
  color: #ffffff;
  background-color: #999999;
  font-size: 20px;
}
/* ロゴ */
div.logo {
  text-align: center;
  margin: auto;
  padding: 10px 0 0 0;
  width: 300px;
  height: 600px;
  border: 1px solid #000000;
  background-color: #FFFFFF;
}
/* 注意書き */
div.warning {
  text-align: center;
}
/* ログインボタン */
div.login-line {
  text-align: center;
}
div.login-button {
  margin: auto;
  display: inline-block;
  padding: 0.5em 1em;
  text-decoration: none;
  background: #668ad8;/*ボタン色*/
  color: #FFF;
  border-bottom: solid 4px #627295;
  border-radius: 3px;
  cursor: pointer;
}
/* フッタ */
div.footer {
  text-align: right;
  width: 100%;
  height: 50px;
  margin: 30px 0 0 0;
  background-color: #EEEEEE;
  font-size: 15px;
}

/* mainページ */
div.main {
  background-color: #000000;
}
div.text-main {
  color: #FFFFFF;
  font-size: 50px;
}

ボタンデザインは外部参考:
CSSで作る!押したくなるボタンデザイン100(Web用)
初心者が適当に定義したデザインと差がありすぎて浮いてる…

実現方針1:(非推奨なやつ)HTML要素の有無を動的に切り替える

HTML上の要素有無を書き替えてしまう方法。
元ネタはHTML上にベース部分があり、この方法は取っていない。
個人的にこちらの方針はイマイチだと思う。理由は後述。

1-1. ()HTML要素を追加/削除する

素直な方法。
非表示にするHTML要素を削除し、表示するHTML要素を追加してやる。
こんな感じでJavaScriptを作って、操作前HTMLに対して動作させる。

・visible.js

function login() {
  // Top要素の削除を実施
  // 1. TopページのDiv要素を取得
  var topDom = document.getElementById('top');
  // 2. Topページの親要素(body)を取得
  var bodyDom = topDom.parentNode;
  // 3. TopページのDiv要素を削除
  bodyDom.removeChild(topDom);
  
  // Main要素の作成と追加
  var mainDiv = document.createElement('div');
  mainDiv.id = 'main';
  mainDiv.classList.add('main');
  bodyDom.appendChild(mainDiv);
  
  // text-mainの作成と追加
  var textMainDiv = document.createElement('div');
  textMainDiv.classList.add('text-main');
  textMainDiv.textContent = "入場に成功しました!!";
  mainDiv.appendChild(textMainDiv);
}
1-1. がダメな理由

HTML内の一部、局所的に書き換えるならこの方法でも良いと思うが、ページ遷移など中~大規模に切り替えるには懸念がある。
HTMLの記述がJavaScriptのコードに内包されてしまい、最終的に出力されるページの構成が見通せなくなってしまうという点。

正直、このサンプルですら出力したいHTMLの構成を読み取るのは面倒。
テンプレートエンジンなどに要素の追加削除を任せられる場合でもなければ、この方法での大規模な要素切り替えは不適切だと思う次第。

1-2. (非推奨なやつ)HTML上でコメントアウト/コメントアウト解除する

無駄に捻くれた方法だと思う。
コメントアウトしたテンプレートを仕込んでおいて、動的にコメントアウト化する部分を切り替える。
まず操作前HTMLはこうする。

<html lang='ja'>
<meta charset="UTF-8"/>
<title>出現/消去のてすとぺーじ</title>
<link rel="stylesheet" type="text/css" href="./common/visible.css"/>
<script type="text/javascript" src="./common/visible.js"></script>
<body>
<div id="top" class="top-page">
  <header>
    <div class="header">TOPページのヘッダです</div>
  </header>
  <div class="logo">ロゴ表示</div>
  <div class="warning"><p>表示非表示を制御するボタンです。</br>気が向いたらクリックしましょう。</p></div>
  <div class="login-line"><div onclick="login()" class="login-button">入場する</div></div>
  <footer>
    <div class="footer">TOPページのフッタです。このHTMLはテストで作りました。</div>
  </footer>
</div>
<div id="main" class="main">
<!--
  <div class="text-main">入場に成功しました!!</div>
-->
</div>
</body>
</html>

で、visible.jsをこうする。

function login() {
  // 1. TopページのDiv要素を取得してコメントアウト
  var topDom = document.getElementById('top');
  topDom.innerHTML  = '<!--' + topDom.innerHTML  + '-->';
  
  // 2.  Main要素を取得してコメントアウトを解除
  var mainDom = document.getElementById('main');
  var newHtml = mainDom.innerHTML ;
  // コメントアウトの先頭を削除
  newHtml = newHtml.replace(/<!--/, '');
  // コメントアウトの末尾を削除
  newHtml = newHtml.substr(0, newHtml.lastIndexOf('-->'));
  // 要素を置き換え
  mainDom.innerHTML  = newHtml;
}
1-2. がダメな理由

書く前にわかってたけどこれはない。
利用部分をコメントで制御するってのが直感的じゃないし、操作のサポートもなくて無理やり組み込んでるし。
コメントはちゃんとコメントのために使うべき。

実現方針2:「表示しない」スタイルに切り替えて制御する

CSSで「表示しない」ためのスタイルを作って、そのスタイルを追加/削除することで制御する。
まず、操作対象とするHTMLはこれ。

<html lang='ja'>
<meta charset="UTF-8"/>
<title>出現/消去のてすとぺーじ</title>
<link rel="stylesheet" type="text/css" href="./common/visible.css"/>
<script type="text/javascript" src="./common/visible.js"></script>
<body>
<div id="top" class="top-page">
  <header>
    <div class="header">TOPページのヘッダです</div>
  </header>
  <div class="logo">ロゴ表示</div>
  <div class="warning"><p>表示非表示を制御するボタンです。</br>気が向いたらクリックしましょう。</p></div>
  <div class="login-line"><div onclick="login()" class="login-button">入場する</div></div>
  <footer>
    <div class="footer">TOPページのフッタです。このHTMLはテストで作りました。</div>
  </footer>
</div>
<div id="main" class="main hidden">
  <div class="text-main">入場に成功しました!!</div>
</div>
</body>
</html>

でもって、visible.jsはこれ。

function login() {
  // 1. Topページのスタイルにhiddenを追加
  document.getElementById('top').classList.add('hidden');
  
  // 2.  Mainページのスタイルからhiddenを削除
  document.getElementById('main').classList.remove('hidden');
}

CSSのスタイル"hidden"を定義することで、非表示を実現することになる。

2-1. CSSのdisplay: none で制御する

非表示で調べるとぱっと出てくるのはこれか。
display: noneのスタイルで表示しないという制御が可能。
visible.cssに下記を追加して実現する。

/* hiddenの設定 */
div.hidden {
  display: none;
}
CSS - displayの定義

参考:Visual formatting model

設定値 下記いずれか
inline, block, list-item, inline-block, table, inline-table,
table-row-group, table-header-group, table-footer-group, table-row,
table-column-group, table-column, table-cell, table-caption,
none, inherit
初期値 inline
適用先 全要素
継承 継承されない

設定値は多いので、代表的なものとnoneの説明のみ。

block
ブロック要素として縦に配置する。
縦横のpadding,marginwidth,heightを指定可能。
※ 要素内側の余白がpaddingで、外側の余白がmargin。
inline
行内で配置する。
横のpaddingmarginを指定できるが、widthheightは指定不可。
inline-block
行内でブロック要素として配置する。
横に配置されるがブロックなので、縦横のpadding,marginwidth,heightを指定可能。
none
要素が表示されないようにする。


2-2. CSS:opacity(不透明度)で透明にする

要素の不透明度を設定できる。これで透明にすれば非表示という考え方。
「永遠ちゃんねる」はdisplayとこれの合わせ技っぽい。

サンプルに適用したときは透明にしただけだと配置場所がずれたので、無理やり大きさを0にする設定を入れることになった。何かいい方法があるのかな…
あと、クリックイベントが残るので無効化も必要。

visible.cssに下記を追加して実現。

/* hiddenの設定 */
div.hidden {
  opacity: 0.0;
  width 0 !important;
  height: 0 !important;
  pointer-events: none; /* クリック無効化 */
}
CSS - opacityの定義

参考:CSS Color Module Level 4

設定値 アルファ値(0.0~1.0)
初期値 1(不透明)
適用先 全要素
継承 継承されない

要素の不透明度を0.0(透明)~1.0(不透明)の間で定義できる。基本的には半透明にして表示を重ねたい場合に使う認識。

非表示のためだけにこれを使うメリットは…思いつかなかった。
クリックイベントが残る、Ctrl+Aの選択範囲に入るといった特徴があるが、透明なのにそれらの挙動が残るとユーザ操作として直感的ではない気がしてしまう。

SEOやら広告的意味やら、自分が調査しきれてないレベルでは何かメリットがあるのかもしれない。


2-3. CSS:positionの設定で画面外に追いやる

要素の出力位置を絶対指定にして、ありえない負数を入力することで画面外に追いやる方法でも、見た目の非表示は実現できる。

visible.cssに下記を追加して実現。

/* hiddenの設定 */
div.hidden {
  position: absolute;
  top: -10000px;
  left: -10000px;
}
CSS - positionの定義

多分このあたりに載ってる:CSS Positioned Layout Module Level 3
詳細調査は機会があれば。
まあ、非表示を実現するために使うのは避けたほうがいいと思う。
実現できるかどうかが要素の大きさに依存するし、単純にソース読んでこれが出てきたらなんで?って疑問に思う。


まとめ

  • 動的なページ切り替えは直接要素を追加/削除する方法、スタイルで表示箇所を制御する方法の2通り考えられる。
  • 直接追加/削除の方法は局所向けの認識。素組みで大規模にやるとコードの見通しがきつい。
  • スタイルで非表示を制御するだけならCSSdisplay: noneがシンプル。
  • CSSでopacity: 0.0でも非表示スタイルは実現できるが、クリックイベントが残ったり選択範囲に入ったり制約あり。
  • HTMLもCSSも自分が知らないこと多すぎ。