1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

 簡単なタブシステムを作ってみる。

タブシステムとは?

 cssの定番、タブシステム。ボタンが上の方に並んでいて、クリックでコンテンツを切り替えることができる。

OpenProcessingのスケッチ置き場を間借りしました。で、こんな感じの物を作る。

wwvwvwv.png

上の方にtext0,text1,text2とボタンが並んでて、クリックで切り替えられる。text0とtext1には枕草子のダミーテキストを配置しました。

お借りしました!それで、これを実現しようというわけです。なおtext2のタブには400x400のキャンバスが置いてあります。ほんとうはp5でなんか描いても良かったんですが、p5はすでにあるキャンバスになんかスケッチを描くのが凄く不便なので諦めてしまいました。ごめんなさい。

コード全文

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <link rel="stylesheet" type="text/css" href="style.css">
  </head>
  <body>
  	<main id="main">
      <div id="header">
        <button class="tab active" id="tab0">text0</button>
        <button class="tab" id="tab1">text1</button>
        <button class="tab" id="tab2">text2</button>
      </div>
      <div id="contents">
        <div class="content open">
          <div id="content0">
            内容0
          </div>
        </div>
        <div class="content">
          <div id="content1">
            内容1
          </div>
        </div>
        <div class="content">
          <div id="content2">
            内容2
          </div>
        </div>
      </div>
      <div id="footer">
      	Footer
      </div>
  	</main>
  	<script src="main.js"></script>
  </body>
</html>
style.css
/* grid-layout: https://developer.mozilla.org/ja/docs/Web/CSS/CSS_grid_layout/Grid_template_areas */

html {
  font-size: 62.5%;
}

* {
  box-sizing: border-box;
}

body {
  width:100%;
  height:100dvh;
  font-size:1.8rem;
  background:rgb(200,200,255);
  min-width:390px;
  margin: 0; /* bodyの既定のマージンを取り除く。既定のマージンうざすぎ */
}

#main{
  display:grid;
  grid-template-rows:40px 1fr 60px;
  width:100%;
  height:100%;
}

#header{
  display:flex;
}

.tab{
  margin:5px 5px 0 5px;
  width:100px;
  border-style:none;
  border-radius: 10px 10px 0px 0px;
  background-color:rgb(180 180 180);
  font-size:1.6rem;
}

.active{
  background-color:white;
}

#contents{
  padding:20px;
  background-color:white;
  overflow:scroll;
}

.content{
  margin:0;
  display:none;
}

.open{
  display:block;
}

/* 中央揃え:https://www.freecodecamp.org/japanese/news/how-to-center-anything-with-css-align-a-div-text-and-more/ */
#footer{
  font-size:2.2rem;
  background-color:black;
  /* 水平・垂直方向の中央揃え */
  display: flex;
  justify-content: center;
  align-items: center;
  color:white;
}
main.js
// 出典:https://bungobungo.jp/text/hakbn/

const text0 = "省略";

const text1 = "省略";

const text2 = "省略";

window.onload = (function(){
  const contents = new Array(3);
  const tabs = new Array(3);
  for(let i=0; i<3; i++){
    contents[i] = document.getElementById(`content${i}`);
    tabs[i] = document.getElementById(`tab${i}`);
  }

  const cvs = document.createElement("canvas");
  cvs.width = 400;
  cvs.height = 400;
  const ctx = cvs.getContext("2d");
  contents[2].after(cvs);

  let is_loop = false;
  let properFrameCount = 0;

  const loopFunction = () => {
    ctx.setTransform(1,0,0,1,0,0);
    ctx.fillStyle = "black";
    ctx.fillRect(0, 0, 400, 400);
    ctx.fillStyle = "white";
    ctx.font = "32px italic sans-serif";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    const angle = Math.PI*2*properFrameCount/240;
    ctx.setTransform(1,0,0,1,200+100*Math.cos(angle), 200+100*Math.sin(angle));
    ctx.transform(Math.cos(angle*2), Math.sin(angle*2), -Math.sin(angle*2),Math.cos(angle*2),0,0);
    ctx.fillText("枕草子", 0, 0);
    if(is_loop) window.requestAnimationFrame(loopFunction);
    properFrameCount++;
  }

  contents[0].innerText = text0;
  contents[1].innerText = text1;
  contents[2].innerText = text2;

  for(let i=0; i<3; i++){
    const tab = tabs[i];
    tab.addEventListener("click", () => {
      for(let k=0; k<3; k++){
        tabs[k].classList.remove("active");
        contents[k].parentNode.classList.remove("open");
      }
      tabs[i].classList.add("active");
      contents[i].parentNode.classList.add("open");
      if(i===2){
        is_loop = true;
        window.requestAnimationFrame(loopFunction);
      }else{
        is_loop = false;
      }
    });
  }
  // ほんとはp5でやろうと思ったんですが
  // APIが柔軟性に欠けるので諦めました。

});

解説

フォントサイズ

 htmlに62.5%を指定してあとはremで調節する方法に頼っています。個別にpxで指定するより柔軟性が高いらしい...詳しくは知らないです。

box-sizingについて

 border-boxにするとborder込みでサイズを計算してくれるのでありがたいようです。このスケッチではborderを使ってないのであんま意味がないかもしれません。

mainタグの構成

 グリッドレイアウトを使っています。これが一番便利だと思いました。ヘッダーとフッター用に40px,60pxを確保して残りをコンテンツに当てています。今回は全画面なのでそういう構成ですが、部分的にタブシステムを作る場合はそれ相応の構成になるものと思われます。すべてのケースに対応するのは難しいので今回はこれで。
 なおbodyの100dvhというのはvhではなくdvhなんですが、サイトによっては独自のバーがあったりする、それを考慮したうえでの画面サイズを採用してくれるので便利なんだそうです。

ヘッダーの構成

 flexを使ってボタンを並べています。横一列です。marginで間を空けて、上の方を丸くしています。かわいい!色は灰色ですが、アクティブな時に白くなるようにできています。

.tab{
  margin:5px 5px 0 5px;
  width:100px;
  border-style:none;
  border-radius: 10px 10px 0px 0px;
  background-color:rgb(180 180 180);
  font-size:1.6rem;
}

.active{
  background-color:white;
}

コンテンツの構成

 コンテンツはボタンの数と同じだけ用意しています。開いてるところだけopenを付与しています。これもいろんなやり方があるっぽいんですが自分はクラスの付け替えがしっくり来たのでそれを使っています。

.content{
  margin:0;
  display:none;
}

.open{
  display:block;
}

css面白いですよね。まあjsで何でもかんでもできるわけないんで、css勉強した方がいいのは当たり前なんですが...はっきり言って何にも分からない世界なので、毎日楽しいです。もっと勉強しようっと。

フッターの構成

 フッターは文字を真ん中に置きたかったんですが、たったそれだけでさえ普通にやるとかなりめんどくさいようです。そこでflexの出番です。これを使うと内部のコンテンツを簡単に中央揃えできます。

#footer{
  font-size:2.2rem;
  background-color:black;
  /* 水平・垂直方向の中央揃え */
  display: flex;
  justify-content: center;
  align-items: center;
  color:white;
}

スクリプトの構成

 スクリプトの仕事はボタンを押したときにコンテンツのクラスを付け替えて、表示と非表示を切り替えることです。それに加えて、text2のタブにどうでもいいスケッチを追加しました。まあ何にもお絵描きしないのはさみしいので。

  const contents = new Array(3);
  const tabs = new Array(3);
  for(let i=0; i<3; i++){
    contents[i] = document.getElementById(`content${i}`);
    tabs[i] = document.getElementById(`tab${i}`);
  }

ここでボタンとコンテンツの配列を作っています。なおすべての処理はonloadの後で実行しています。
次にイベントリスナーを登録します。

  for(let i=0; i<3; i++){
    const tab = tabs[i];
    tab.addEventListener("click", () => {
      for(let k=0; k<3; k++){
        tabs[k].classList.remove("active");
        contents[k].parentNode.classList.remove("open");
      }
      tabs[i].classList.add("active");
      contents[i].parentNode.classList.add("open");
      if(i===2){
        is_loop = true;
        window.requestAnimationFrame(loopFunction);
      }else{
        is_loop = false;
      }
    });
  }

ボタンのactiveを付け替えることで色などを変化させています。ついでにコンテンツの親のクラスも付け替えることで開いたり閉じたりしています。そこにcssが機能するとDOMが現れたり消えたりします。楽しい!
 キャンバスについて。

  const cvs = document.createElement("canvas");
  cvs.width = 400;
  cvs.height = 400;
  const ctx = cvs.getContext("2d");
  contents[2].after(cvs);

  let is_loop = false;
  let properFrameCount = 0;

  const loopFunction = () => {
    ctx.setTransform(1,0,0,1,0,0);
    ctx.fillStyle = "black";
    ctx.fillRect(0, 0, 400, 400);
    ctx.fillStyle = "white";
    ctx.font = "32px italic sans-serif";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    const angle = Math.PI*2*properFrameCount/240;
    ctx.setTransform(1,0,0,1,200+100*Math.cos(angle), 200+100*Math.sin(angle));
    ctx.transform(Math.cos(angle*2), Math.sin(angle*2), -Math.sin(angle*2),Math.cos(angle*2),0,0);
    ctx.fillText("枕草子", 0, 0);
    if(is_loop) window.requestAnimationFrame(loopFunction);
    properFrameCount++;
  }

ほんとはp5でやりたかったんですが、なんかめんどくさかったのでやめました。p5は厳密にはコンテキストのラッパでは無いんですよね。キャンバス自体の生成からやってくれるので。つまりhtmlとかjsとかなんもわからん人のためのライブラリであるがゆえに柔軟性が低いので使いづらいです。それで生でやってるんですが、有志の人がきっとp5で書き換えてくれると思うので期待しましょう。

おわりに

 タブシステム作ったんですがいろいろ勉強になって楽しかったです。拝読感謝。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?