まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

タグ:css

先日、IBM から無料で商用利用可能な和文フォント IBM Plex Sans JP が公開されました:
無料で商用利用可能な和文フォント「IBM Plex Sans JP」をIBMがリリース

00_m

上記ページで紹介されているようにまとめてダウンロードしてインストールすれば、自分の PC 内のワードやパワーポイント作成時に使えるようになります。 が、自分はプログラマーなのでプログラマーらしくというか、このフォントを使ってウェブページを作り、同フォントをインストールしていないユーザーにもこのフォントで文字が表示されるようなウェブページを作る場合の CSS 指定方法を紹介します。

まず、ページを作る立場の人はフォントをダウンロードする必要があります。上述の Gigazine 様のページではフォントをまとめてダウンロードする方法が紹介されていますが、ここでは特定の1つのフォントだけをダウンロードする方法を紹介します(どちらでもいいです)。

実際の和文フォントが格納されているフォルダを指定してウェブページを開きます:
https://github.com/IBM/plex/tree/master/IBM-Plex-Sans-JP/fonts/complete/otf/hinted

2021072601


ここから目的のフォントを1つ(2つ以上でもいいですけど・・)選んでダウンロードします。とりあえず普通そうな(最後に Regular とある)フォント IBMPlexSansJP-Regular.otf を選んでダウンロードします:
2021072601


そして HTML(CSS) を編集します。先程ダウンロードしたフォントファイル: IBMPlexSansJP-Regular.otf を同じフォルダに入れた状態で、HTML 内または CSS で以下のようなスタイルシートを記述します:
@font-face{
  font-family: IBMPlexSansJP;
  src: url("./IBMPlexSansJP-Regular.otf");
}

.ibmplex{
  font-family: IBMPlexSansJP;
}

これが有効になった(読み込まれた)状態の HTML 内で <p class="ibmplex" style="font-size: 1em;">あいうえお</p> のように記述すると、クラスを指定した要素は IBMPlexSansJP-Regular.otf フォントで表示されるようになります:


あとはこの HTML, CSS とフォントファイルをまとめてサーバーにアップロードすれば、フォントが埋め込んだページが公開されます。サンプルとしてはこんな感じ:
https://dotnsf.github.io/plex-jp/
<style>
@font-face{
  font-family: IBMPlexSansJP;
  src: url("./IBMPlexSansJP-Regular.otf");
}

.ibmplex{
  font-family: IBMPlexSansJP;
}
</style>

  :
  :

<body>

<h1 class="ibmplex" style="font-size: 2em;">新フォントの紹介</h1>

<div class="container">
  <p class="ibmplex" style="font-size: 1em;">IBM Plex Sans 日本語フォントが公開されました。</p>
  <p style="font-size: 1em;">ここはシステムフォントです。</p>
</div>

</body>

2021072602


ここで紹介した CSS 自体は IBM Plex Sans JP フォント以外でも使える方法ですが、商用利用可能な無料の日本語フォントが貴重なのと、ネットでの評判もまあまあいい感じなので、ぜひ多くの人に IBM Plex Sans JP を使ってみていただきたいです。


 

スマホのカメラを HTML ページから起動するには、以下のようなタグを用意する方法があります:
<input type="file" accept="image/*" capture="camera"/>


この方法でボタンからカメラを起動すると、カメラで撮影した画像をこのタグの value として引き渡すことができます。「カメラで画像を撮影してアップロード」する方法としては非常に簡単です。

ただこの方法には1つ大きな難点があります。この方法で起動したカメラは HTML による装飾ができず、常にスマホの全画面を使って写真撮影することになります。つまり「撮影中は決まった UI しか使えない」のです。

具体的には以下のようになります(iOS 14 の場合)。<input> タグ自体は CSS で見た目を変えることができるため、例えばこのような画面を用意して、カメラアイコンをタップするとカメラを起動、させるように作ることができます。この「カメラを起動する前」までの UI は自由にデザイン可能です:
2021060801


しかし実際にカメラを起動するとこのような画面になります。全画面でカメラが有効になり、撮影ボタンを押すことで、その瞬間のカメラ映像を記録できます。ただ「撮影ボタンを押す」というアクションを避けることはできません(画面内に QR コードが認識したら自動的に・・・といったことはできません):
2021060802


撮影すると一度この確認画面になり、「写真を使用」を選択することで処理を続けることができるようになります(再撮影した場合は再度撮影画面を経てこの画面になります):
2021060803


カメラ画面を終了すると元の画面に戻ります。ここからはまた自由に見た目を調整することができ、また既に撮影済みの画像のデータを取得することもできるので(File API などと組み合わせることで)プレビュー表示したり、ここから QR コードを読み取って・・・といったこともできるようになります:
2021060804


といった具合です。撮影後は元の画面に戻るので撮影の前後ではデザイン装飾されたページを参照できるのですが、撮影時だけは(撮影し終わるか、キャンセルするまでは)どうにもできません。シャッターを押す、というプロセスを変えることができない UI なので、シャッターを押すことなく画面内に QR コードを確認したら内容を読み取って次の処理へ・・・といったカスタマイズはできないことを意味しています。簡単に実装できる一方で、この撮影前後でのインターフェースにかなり大きな制約を受けることになります。

今回、なんとかして HTML 画面内で上述のようなこと(シャッターを押すことなく画面内に QR コードを確認したら内容を読み取って次の処理)を実現できないかと考え、一応見た目的にはできそうな目処がたったのでサンプルと合わせて紹介します。


まず、サンプルアプリケーションはこちらで試すことができます。ウェブカメラ付きの PC ブラウザか、カメラ付きスマホのブラウザでこちらにアクセスしてみてください(以下の画像は iOS 14 の Safari ブラウザを使った場合の例です):
https://dotnsf.github.io/html_camera_inside/


まず以下のようなダイアログが表示されてカメラへのアクセス許可を求められる(今回の方法だとこの確認ダイアログを回避することはできないと思います)ので「許可」してください:
2021060805


(詳しくは後述しますが)PC ブラウザの場合はウェブカメラ(つまりフロントカメラ)、スマホの場合は背面カメラが有効になり、カメラが映すストリーム映像がウェブ画面上部に表示され続けます。この画面は HTML および CSS で装飾されているもので、自由にカスタマイズできます(上述の「決まったUIしか使えない」ものとは異なります)。今回の例では HTML 画面内に背面カメラの映像を映し続けるようにしています(この画面は HTML で作られています):
2021060806



実際のアプリケーションではここに「撮影」ボタンなどを用意するなどして、ある瞬間の映像を切り取って画像化したり、その画像をサーバーにアップロードすることも可能です。今回のサンプルではボタンで撮影するわけではなく、映像を常に監視しており、映像内に QR コードが認識されたタイミングでその QR コードデータの内容を表示するようにしています。適当な QR コードを探して、スマホや PC ブラウザの映像に映るようにして、動作を確認してみてください:
2021060807



以上がサンプルアプリケーションの紹介です。以下はソースコードの解説です。


このサンプルアプリケーションのソースコードをこちらで公開しています:
https://github.com/dotnsf/html_camera_inside


実質的なコードは index.html 1つだけです。このコードの中に以下のような記載があります:
  :
  :
  //. video
  video = document.createElement( 'video' );
  video.id = 'video';
  video.width = cameraSize.w;
  video.height = cameraSize.h;
  video.setAttribute( 'autoplay', true ); 
  video.setAttribute( 'muted', '' ); 
  video.setAttribute( 'playsinline', '' );
  document.getElementById( 'videoPreview' ).appendChild( video );

  //. media
  var data = {
    audio: false,
    video: {
      facingMode: 'user', //. front 
      width: { ideal: resolution.w },
      height: { ideal: resolution.h }
    }
  };
  if( isMobile() ){
    data.video.facingMode = 'environment'; //. back
  }
  media = navigator.mediaDevices.getUserMedia( data ).then( function( stream ){
    video.srcObject = stream;
  }).then( function( err ){
  });
  :
  :
<body>
  <!-- video(visible) -->
  <div class="container" id="videoPreview" style="text-align: center;">
    <h4>Video Preview</h4>
  </div>
  :
  :


この部分で「カメラを有効にして <body> 内に <video> 要素を生成して撮影した様子を表示」しています。以下2つに分けて説明します。

まずは青字部分、ここで <video> 要素を動的に生成して、<div id="videoPreview"></div> 内に追加しています。ここまではそんなに難しくないと思ってます。

そして赤字部分です。ここで MediaDevices.getUserMedia() メソッドを使ってカメラを有効にしています。その際に PC ブラウザではフロントカメラを、スマホでは背面カメラを有効にする必要があるのですが、getUserMedia() メソッドの引数となるオブジェクトの video.facingMode 属性を 'user'(フロント)にするか、'environment'(背面)にするかで切り替えています。getUserMedia() メソッドが成功したら、その結果を上述の <video> 属性の srcObject に指定することでカメラで撮影し続ける結果を <video> 要素内に表示することができるようになります。

なお、getUserMedia() メソッドは https でアクセスしている場合のみ利用することができます。したがって同様のアプリケーションを作る場合も https で利用できるサイトにページを設置する必要がある点に注意してください。

これだけでカメラで撮影しつつ、その映像を HTML ページ内に表示する、という所までは実現できます。ただ <video> タグのままだとこの先のデータ取り出しなどが必要になった場合に不便です。そこで <video> タグ内に表示される映像を(コマ送りで)<canvas> に表示するように処理を追加して、<canvas> の画像データを監視したり取り出して処理できるように改良しています(加えて <canvas> 自体は非表示の <div> 内に生成することで画面的には変化がないようにしています):
  :
  :
  //. canvas
  canvas = document.createElement( 'canvas' );
  canvas.id = 'canvas';
  canvas.width = canvasSize.w;
  canvas.height = canvasSize.h;
  document.getElementById( 'canvasPreview' ).appendChild( canvas );

  //. context
  ctx = canvas.getContext( '2d' );

  //. render video stream into canvas
  _canvasUpdate();
  :
  :

//. render video stream into canvas
function _canvasUpdate(){
  //. video to canvas(animation)
  ctx.drawImage( video, 0, 0, canvas.width, canvas.height );

  //. check QR code
  var img = ctx.getImageData( 0, 0, canvas.width, canvas.height );
  var result = jsQR( img.data, img.width, img.height );
  if( result && result.data ){ 
    alert( result.data ); 
  }else{
    requestAnimationFrame( _canvasUpdate );
  }
};

  :
  :

  <!-- canvas(invisible) -->
  <div  style="text-align: center; display: none;" id="canvasPreview" class="container">
    <h4>Canvas Preview</h4>
  </div>


上述の _canvasUpdate() メソッドでは <video> の映像を(コマ送りになるよう)切り出して ctx.drawImage() メソッドで <canvas> に転写しています。転写後に <canvas> から画像データを取り出し、 QR コードライブラリ : jsQR を使ってデコードします。デコードが成功していたら(画面内に QR コードが映っていたと判断して)そのデコード結果を alert() 表示します。デコードが成功しなかった場合は QR コードは映っていなかったことになるので、requestAnimationFrame メソッドを使って再度 _canvasUpdate() メソッドを呼び出します※。これを繰り返すことで <canvas> にも(不自然なコマ送りにならずに)自然な映像を表示することが実現できています。

※<video> と <canvas> を連動させるこの技術は他でも応用できそうな印象です。


なお上述の青字部分の、特に以下の部分(青太字の3行)についてコメントを加えておきます:
  video.id = 'video';
  video.width = cameraSize.w;
  video.height = cameraSize.h;
  video.setAttribute( 'autoplay', true ); 
  video.setAttribute( 'muted', '' ); 
  video.setAttribute( 'playsinline', '' );
  document.getElementById( 'videoPreview' ).appendChild( video );
  :
  :


ここは処理的には video.autoplay = true; という属性が設定されていれば正しく動くはずの部分です。ところが原因はよくわからないのですが、iOS ではこれ以外に muted と playsinline という2つの属性についてもなんらかの設定がされている必要があり、しかもこれらを setAttribute() メソッドを使って設定しないと正しく動かない、という現象が発生している模様です(実際に setAttribute() を使わずに直接設定して動かすと、最初だけ <video> 内に映像は描画されますが、それ以降は映像が更新されなくなってしまいました。そのため <canvas> への映像更新も止まってしまいました)。その対応のため現状のようなコードになっています。詳しくはこちらも参照ください:
Navigator.mediaDevices.getUserMedia not working on iOS 12 Safari



というわけで、色々苦労しつつもなんとか HTML & CSS で装飾可能なカメラ撮影画面をウェブページでも実現することができそうな目処が立ちました。「カメラを起動する」ための便利なタグを使うのではなく、どちらかというと動画撮影の機能を有効にした上で、ある瞬間を切り取って撮影したことにする、というアプローチで実現しています。具体的な UI の実現方法はここで紹介した方法以外にもあると思うので、このサンプルを参考にしつつも色々挑戦してみてください。



今更感がありますが、「ハンバーガーメニュー」と呼ばれるメニューがあります。三本線の見た目がハンバーガーのように見えることから名付けられたメニューで、ここをクリック(タップ)すると隠れていたメニューアイテムが表示されて選択できるようになる、というものです。スマートフォンなどの、メニューを常時表示するには小さい画面でメニューを実現するためのメジャーな方法です。

このハンバーガーメニューを(ウェブアプリの)CSS だけで実現する方法がいくつか紹介されています。まあコピペすれば動くし、自分も以前はそうしていたのですが、ちゃんと理屈を理解してはいませんでした。そこで今回改めて自分で理解しながら作って公開してみました。まずは一度動かしてみて、ハンバーガーメニューの挙動を確認してみてください:
https://dotnsf.github.io/css_hamburgermenu/

(ハンバーガーメニュー)
2021022001

(タップすると左からメニュー本体が表示され、自分は×印に。×をタップで元に戻る)
2021022002


ハンバーガーメニューに限りませんが、このような動きのある処理を実現するには、一般的には JavaScript を使います。またメニューの見た目である三本線や、メニューが開いた時の×印の表現は一般的には画像を使って表現することが考えられます。上図のページではどちらも使わず、純粋な HTML と CSS だけで実現しています。

同じようなコード(HTML & CSS)のサンプルは数多く存在していて、コピー&ペーストすれば動くようにはなっています。ただコードの中身の解説がされていない場合が多く、「なんでこのコードでハンバーガーメニューになるのか」の理解が難しいのでした。

というわけで、どうやって画像も JavaScript を使わずにハンバーガーメニューを実現しているかを、上図の例を使って解説します。


まず、このページの HTML と CSS は以下のようになっています(よかったらコピペするかダウンロードして、手元の HTML ファイルに保存して動かしてみてください):
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8"/>

<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<meta name="apple-mobile-web-app-title" content="CSS Hamburger Menu"/>

<style>
/* ハンバーガーメニューのサイズ(32x32) */
.hamburger-menu{
  vertical-align: center;
  width: 32px;
  height: 32px;
}
/* ハンバーガーメニューの位置と色 */
.menu-btn{
  top: 20px;
  left: 10px;
  display: flex;
  height: 32px;
  width: 32px;
  justify-content: center;
  align-items: center;
  z-index: 90;
  background-color: #005B00;
}
/* メニュー線(本体と before と after で3本表示する) */
.menu-btn span,
.menu-btn span:before,
.menu-btn span:after{
  /* 20x3 の白線 */
  content: '';
  display: block;
  height: 3px;
  width: 20px;
  border-radius: 3px;
  background-color: #ffffff;
  position: absolute;
}
/* before を少し上にずらして描画 */
.menu-btn span:before{
  bottom: 8px;
}
/* after を少し下にずらして描画 */
.menu-btn span:after{
  top: 8px;
}

/* メニューをオープンしたら三本線を×にする */
#menu-btn-check:checked ~ .menu-btn span{
  /* メニューオープン時は三本線の真ん中の線を透明にする */
  background-color: rgba( 255, 255, 255, 0 ); 
}
#menu-btn-check:checked ~ .menu-btn span::before{
  /* メニューオープン時は三本線の上の線を 45 度傾ける */
  bottom: 0;
  transform: rotate( 45deg ); 
}
#menu-btn-check:checked ~ .menu-btn span::after{
  /* メニューオープン時は三本線の下の線を -45 度傾ける */
  top: 0;
  transform: rotate( -45deg ); 
}
/* チェックを非表示にする */
#menu-btn-check{
  display: none;
}

/* メニュー装飾 */
.menu-content{
  width: 60%;
  height: 100%;
  position: fixed;
  top: 80;
  left: -100%; /* メニューを画面外へ */
  z-index: 80; /* 下のコンテンツの上にかぶせて表示する */
  background-color: #005B00;
  transition: all 0.5s; /* 0.5秒かけてアニメーションで出し入れする */
}
.menu-content ul{
  padding: 70px 10px 0;
}
.menu-content ul li{
  border-bottom: solid 1px #ffffff; /* メニューアイテムの区切り線 */
  list-style: none;
}
.menu-content ul li a{
  display: block;
  width: 100%;
  font-size: 15px;
  box-sizing: border-box;
  text-decoration: none;
  color: #ffffff;
  padding: 9px 15px 10px 0;
  position: relative;
}

/* メニューの出し入れ */
#menu-btn-check:checked ~ .menu-content{
  left: 0;  /* チェックされたら画面内へ */
}
</style>

<body>

<span class="header">
  <div class="hamburger-menu">
    <!-- ここからメニューボタン -->
    <input type="checkbox" id="menu-btn-check"/>
    <label for="menu-btn-check" class="menu-btn"><span></span></label>
    <!-- ここまでメニューボタン -->

    <!-- ここからメニュー本体 -->
    <div class="menu-content">
      <ul>
        <li><a href="#">メニューアイテム1</a></li>
        <li><a href="#">メニューアイテム2</a></li>
        <li><a href="#">メニューアイテム3</a></li>
      </ul>
    </div>
    <!-- ここまでメニュー本体 -->
  </div>
</span>

</body>
</html>

画像も JavaScript も使っていません。CSS もすべてこの中に含まれていて外部ファイルを参照していません。本当に「これだけ」です。この内容を index.html として保存し、ローカルのブラウザで開けば同じ挙動を確認できるはずです。

HTML の <body> 部分だけを抜き出すとこのようになっています:
<body>

<span class="header">
  <div class="hamburger-menu">
    <!-- ここからメニューボタン -->
    <input type="checkbox" id="menu-btn-check"/>
    <label for="menu-btn-check" class="menu-btn"><span></span></label>
    <!-- ここまでメニューボタン -->

    <!-- ここからメニュー本体 -->
    <div class="menu-content">
      <ul>
        <li><a href="#">メニューアイテム1</a></li>
        <li><a href="#">メニューアイテム2</a></li>
        <li><a href="#">メニューアイテム3</a></li>
      </ul>
    </div>
    <!-- ここまでメニュー本体 -->
  </div>
</span>

</body>

<body> はハンバーガーメニューのボタン部分と、ボタンをタップした時に表示されるメニュー本体部分に分かれています。メニュー本体はそれほど難しくないので、このブログエントリではボタン部分の CSS を中心に、以下の4つの観点から詳しく紹介します:

(1) JavaScript なしでどうやってタップされる前と後を識別しているのか?
(2) 「三本線」の見た目を画像を使わずにどうやって実現しているのか?
(3) (タップ後の)×印の見た目を画像を使わずにどうやって実現しているのか?
(4) JavaScript なしでどうやってメニュー本体の出し入れを実現しているのか?



(1) JavaScript なしでどうやってタップされる前と後を識別しているのか?

<body> のハンバーガーメニューボタン部分に以下のコードが含まれています:
    <input type="checkbox" id="menu-btn-check"/>

この部分はチェックボックスですが CSS で非表示に設定されているので、画面上には現れていません:
/* チェックを非表示にする */
#menu-btn-check{
  display: none;
}

メニューをタップするとチェックが入り、もう一度タップするとチェックが外れます(ただし画面には表示されません)。このチェックの有無でタップされる前と後を識別しているのでした。

また HTML ではこの部分の直後に <label for="menu-btn-check" class="menu-btn"><span></span></label> があります。後述しますがこの <span></span> 部分で三本線や×印を描画します。つまり三本線部分をタップすることで、このチェックボックスのチェックが入ったり消えたりするようになっていて、このチェックの有無で三本線か×印か、どちらを描画するかを切り替えられるような仕組みを実現しています。

以下の説明をわかりやすくするために、ここで一時的にチェックボックスの非表示スタイルを解除してみましょう。以下は該当 CSS 部分にこのようなコメントが入っている前提で説明を続けます:
/* チェックを非表示にする */
#menu-btn-check{
/*  display: none; */
}

この時点でハンバーガーメニューや×印の上部に(今まで非表示だった)チェックボックスが表示されているはずです:

2021022003

2021022004


(2) 「三本線」の見た目を画像を使わずにどうやって実現しているのか?

今回のブログを書くために色々調べる前は、ここが最大の謎でした。この三本線が画像でないとしたら何? SVG で描画しているのかと思ったけどそういう記述はないし・・・ と興味津々だった謎でした。

これを実現している HTML 及び CSS 部分は以下です:
(HTML)
<label  class="menu-btn" for="menu-btn-check"><span></span></label>

(CSS)
/* メニュー線(本体と before と after で3本表示する) */
.menu-btn span,
.menu-btn span:before,
.menu-btn span:after{
  /* 20x3 の白線 */
  content: '';
  display: block;
  height: 3px;
  width: 20px;
  border-radius: 3px;
  background-color: #ffffff;
  position: absolute;
}
/* before を少し上にずらして描画 */
.menu-btn span:before{
  bottom: 8px;
}
/* after を少し下にずらして描画 */
.menu-btn span:after{
  top: 8px;
}

HTML の <span> 部分に線を描画します。そのため CSS で背景色(#ffffff = 白)、高さ(3px)、幅(20px)の塗りつぶし矩形を描画しています。実際は白い矩形ですが、20x3 と横に細長いので白線に見えるというわけです。親要素である .menu-btn の指定で縦横とのセンタリングされて(つまり 32x32 のメニューボタンの真ん中に)表示されます。

これだけだと線が1本表示されるだけですが、CSS では span, span:before, span:after すべてで同じ指定がされています。つまり before と after も合わせると3本の線が描画されることになります(まさかこんな方法で3本描いていたとは・・。この方法だと4本に増やすことはできないかも。。)。別途 before は bottom:8px; で本体よりも少し上に、after は top:8px; で本体よりも少し下に描画するよう指定しており、これらの結果3本の白線が縦に並んでハンバーガーメニューとして表示されているのでした。これ考えた人天才だな。。

2021022005



(3) (タップ後の)×印の見た目を画像を使わずにどうやって実現しているのか?

今回の調査をする前に一番の謎だったのが (2) とすると、調査し終わって一番感動したのがこの (3) の実現方法でした。前述の方法で三本線が表示できるとして、これをタップした時にどうやって表示内容を×印に(画像も JavaScript も使わずに)切り替えるのか、です。

上述したように、まずタップしたかどうかはチェックボックスのチェックの有無で判断できるようになっています。つまり #menu-btn-check:checked の時の .menu-btn の見た目を三本線から×印に変えればよい、ということになります。ここまではなんとなくわかります。

では今度はどのようにして×印の見た目を作るのか、その答えがこちらです。チェックが付いている時の span, span:before, span:after に適用しているスタイルです:
/* メニューをオープンしたら三本線を×にする */
#menu-btn-check:checked ~ .menu-btn span{
  /* メニューオープン時は三本線の真ん中の線を透明にする */
  background-color: rgba( 255, 255, 255, 0 ); 
}
#menu-btn-check:checked ~ .menu-btn span::before{
  /* メニューオープン時は三本線の上の線を 45 度傾ける */
  bottom: 0;
  transform: rotate( 45deg ); 
}
#menu-btn-check:checked ~ .menu-btn span::after{
  /* メニューオープン時は三本線の下の線を -45 度傾ける */
  top: 0;
  transform: rotate( -45deg ); 
}

まず三本線の真ん中の白線(span)については background-color: rgba( 255, 255, 255, 0 ); のスタイルを適用します。これはいわば「透明にする」ためのスタイルで、要するに真ん中の白線を透明にして見えなくします。

次に三本線の上の白線(span:before)は bottom:0; で真ん中の白線と同じ高さに移動した上で transform: rotate( 45deg ); を適用します。この指定は「45度回転させる」スタイルです。つまり水平線(ー)だったものをバックスラッシュ(\)のような線に変えています。

同様に三本線の下の白線(span:after)も top:0; で真ん中の白線と同じ高さに移動した上で transform: rotate( -45deg ); を適用してます。この指定は「-45度回転させる」スタイルであり、水平線(ー)だったものをスラッシュ(/)のような線に変えています。
2021022005


その結果が×印になっていたのでした。眼からウロコ、そんな方法だったのか・・・


(4) JavaScript なしでどうやってメニュー本体の出し入れを実現しているのか?

最後のこの部分は、ちゃんと勉強し直す前から自分で理解できていた、という意味で「一番簡単な仕組み」でした。大まかな仕組みとしてはメニューそのものを作った後、チェックボックスにチェックが入っていない時は表示されない位置に配置し、チェックが入ったら表示される位置まで transition 指定でアニメーションをかけてあげればよいだけのことです(そしてチェックが外れたら再び表示されない位置までアニメーションで戻す)。

上記のコード内では以下のように指定しています:
/* メニュー装飾 */
.menu-content{
  width: 60%;
  height: 100%;
  position: fixed;
  top: 80;
  left: -100%; /* メニューを画面外へ */
  z-index: 80; /* 下のコンテンツの上にかぶせて表示する */
  background-color: #005B00;
  transition: all 0.5s; /* 0.5秒かけてアニメーションで出し入れする */
}
  :
  :
/* メニューの出し入れ */
#menu-btn-check:checked ~ .menu-content{
  left: 0;  /* チェックされたら画面内へ */
}


メニュー本体は .menu-content クラスで定義された部分です。チェックがついていない時点ではここに left: -100%; を指定することで画面の幅1つぶん左の位置に表示しておきます。

そしてチェックが付いた時のスタイルで left: 0; を適用します。これでチェックが付くと画面の左端からメニューが表示されるようになります(チェックが再び外れると、また画面の左端の更に左へ移動して見えなくなります)。この時に .menu-content クラス内で transition: all 0.5s; を適用しておくことで、これらの移動を 0.5 秒かけたアニメーション処理で行うようになります。つまりチェックが付くと左端からすーっと表示され、チェックが外れると左端にすーっと吸い込まれて見えなくなる、という視覚効果をつけることができるのでした。


これら (1), (2), (3), (4) をあわせて適用することで、(画像も JavaScript も使わずに)CSS だけでハンバーガーメニューを実現することができる、というものでした。この「CSS だけで実現する」というのは、「スマホに適した処理として実現できる」とも言えます(簡単に言うと「GPU 併用で処理できるので、比較的貧弱なスマホの CPU に大きな負担をかけずに実現できる」ということです。詳しくはこちら)。この CSS だけの方法だとハンバーガーメニューの見た目を更に変更するのはなかなか難しいなどの制約もありますが、「工夫次第でここまでできる」というクラフトマンシップというか、ある意味天才的な発想を垣間見たような気分で、今後もこの方法を使っていきたいと思う内容でした。

で、動作を確認し終わったら最初にコメントしたチェックボックスの非表示設定はもとに戻しておきましょう。
/* チェックを非表示にする */
#menu-btn-check{
  display: none;
}






2021 年、あけましておめでとうございます。昨年は人の集まりを自粛する機会が多く、不便に感じることも多くありましたが、一方で「オンライン」や「オフライン」であることを強く意識しながら自分のエンジニアとしてのスキルアップを考える機会も多くありました。今年は考えたり勉強したりしたことを色々な形でアウトプットできるよう心がけていこうと思っています。本年もよろしくおねがいします。


さて、そんな新年最初のブログエントリですが、簡易的な HTML プレビューワーを作ってみました。自分はウェブ関連の技術を紹介する機会が公私で多くあるのですが、そこで本当に紹介したい内容を話す際に HTML や CSS, JavaScript といったフロントエンドの基礎技術は理解しているという前提で紹介することがほとんどです。でも実際にはこの前提を満たしていない人に紹介するケースもあります。

考えようによっては「これから色んなことを勉強していきたい」という強い意志を持った人であって、そういう人が自分の話に興味を持ってもらえることは光栄でもあるのですが、とはいえこれらの基礎技術を理解できていない人にフロントエンドのコードを見せても(しかもここは理解している前提なので、詳しい解説を加えるのは別の部分)おそらくちゃんとは理解できないと思っています。願わくば、そのまま基礎技術にも興味を持ち続けて、自分で勉強も続けていただくことで、いつか理解の点と点がつながる日がやってくることが期待できるとも言えます。が、やはり前提を理解している人向けの説明では本来の難易度以上に難しく聞こえてしまうのか、説明している私からも手応えを感じにくい結果となってしまうことがほとんどです。

で、そういった人向けに、ある程度はその場でも(必要以上のツールのインストールなどをしなくても)HTML などの最低限のフロントエンド基礎をオンラインで独習したり、ある程度理解している人であればライブデモのようなことができるようなものを作ってみました。必要なものはオンラインのネット環境とブラウザだけで、オンラインミーティングとの併用でハンズオンを行う場合の相性がいいと思っています。ただテキスト編集の要素もあるツールなので、UI はスマホやレスポンシブではなく PC からの利用を想定しています(タブレットであればまあなんとか・・かも)。


【サービスの URL】
このツールは GitHub Pages を使って公開しています。URL はこちらです:
https://dotnsf.github.io/jch/


PC のブラウザからアクセスすると下のような4つの列からなる画面が表示されます:
2021010501


画面内の4つの列は左から JavaScript, CSS(スタイルシート), HTML, そしてこれらをまとめたプレビューです。左3つが編集エリアになっていて、その編集内容に応じて右のプレビュー結果が変わるようになっています。JavaScript や CSS に外部ライブラリを追加することはできませんが、jQuery だけは初めから使えるようにロード済みです。

初期状態はそれぞれ以下のようになっています:
JavaScript
2021010502

jquery.js をロードしています。そして $(function(){ と }); の間がテキストエリアになっていて JavaScript を記述できるようになっています(初期状態では空です)。ロード済みなので jQuery のセレクタを使った記述も可能です。

変更を加えてから "JavaScript" ボタンをクリックすると、変更内容がプレビューに反映されます。


CSS
2021010503

<style> と </style> の間がテキストエリアになっていて、スタイルシートの指定を自由に記述することができるようになっています(初期状態では空です)。

変更を加えてから "HTML" ボタンをクリックすると、変更内容がプレビューに反映されます。


HTML
2021010504

ベースとなる HTML が記述されています。初期状態では <h2> の見出しが1つと、<div> に囲われたリスト(<ul>~</ul>)が1つ記述されています。

変更を加えてから "HTML" ボタンをクリックすると、変更内容がプレビューに反映されます。


プレビュー結果
2021010505

上述の JavaScript, CSS, HTML の内容をすべて反映した結果がここに表示されます。初期状態では JavaScript と CSS が実質的に空なので、HTML に記述された内容(<h2> の見出しと <ul> のリスト)がそのまま表示されています。

ではこの初期状態を改良すべく、少しずつ編集してみましょう。まずは HTML に以下の赤字の行を加えてみます:
<h2> 50音 </h2>
<div id="xxx">
<ul>
<li id="li1" class="li">あいうえお</li>
<li id="li2" class="li">かきくけこ</li>
<li id="li3" class="li">さしすせそ</li>
<li id="li4" class="li">たちつてと</li>
<li id="li5" class="li">なにぬねの</li>
<li id="li6" class="li">はひふへほ</li>
<li id="li7" class="li">まみむめも</li>
</ul>
</div>

そして上部の "HTML" と書かれたボタンをクリックします。すると追加した2行のぶんが反映されて、プレビュー結果は以下のように変わります:
2021010506


次は CSS を追加してみます。初期状態では空だった <style> と </style> の間のスタイルシート編集部分に以下を追加します:
h2{
  font-size: 50px;
  color: 'blue';
}
#li3{
  color: '#f0f';
}

見出しである <h2> タグのフォントサイズ(50px)と色(青)を指定し、id="li3" の要素(さしすせそ)のみ紫色になるよう指定しています。そして最後に上部の "CSS" ボタンをクリックして、指定通りのプレビュー結果になることを確認します(下図では HTML 列は表示していません):
2021010507


続けて JavaScript も編集してみましょう。こちらも空だったテキストエリア内に以下を追加します:
$('#xxx').css( 'background', 'yellow' );
$('#li4').css( 'color', 'green' );

リストをラップしている <div id="xxx"> タグの背景色(黃)を指定し、id="li4" の要素(たちつてと)のみ緑色になるよう指定しています。そして最後に上部の "JavaScript" ボタンをクリックして、指定通りのプレビュー結果になることを確認します(下図では CSS 列と HTML 列は表示していません):
2021010508


HTML, CSS, JavaScript すべての変更が反映されたプレビュー結果が表示されることを確認できました。もちろんこのまま HTML, CSS, JavaScript に(順番関係なく)変更を追加していく都度、プレビューを確認することができます:
2021010509


このサービスを使うことで、最小限の準備作業で HTML や CSS, JavaScript といったフロントエンドの基礎技術を実際に編集しては結果を確認しながら学んでいくことができるようになると思っています。


【ツールのソースコード】
このサービスは上述のように GitHub Pages を使って公開していますが、実はページそのものもフロントエンドだけで作られています(アイコン画像などは別ファイルですが、サーバーサイドのバックエンド技術は使っていません)。要するにこのサービスも1枚の HTML とその中に記述された CSS, JavaScript だけで実現しています。

このサービスのソースコードも公開しているので、興味ある方は参照ください:
https://github.com/dotnsf/jch


特に該当 HTML ページのソースコードはこちらから参照できます:
https://github.com/dotnsf/jch/blob/main/index.html

ウェブアプリケーションで QR コードを表示したくなることがあります。スマホを持っている人にその場でとあるURLにアクセスしてもらったり、文字列などの情報を送るのに便利な方法だと思っています。

そんな機能が必要な場合、自分は jquery.qrcode.js を使っています。ブラウザの JavaScript(jQuery)だけで文字列の QR コードを簡単に生成することができます。サンプルページを用意したので、こちらで試すとわかりやすいと思います:
https://dotnsf.github.io/jquery_qrcode/

2020110601


画面最上部のテキストフィールドに URL や文字列を入力し、青いボタンを押すと入力した文字列から QR コードを生成して表示する、というものです。この部分は以下のようなコードで実現しています:
  :

<script src="./qrcode.min.js"></script>

  :

<div class="container">
  <input type="text" id="text" value=""/><button class="btn btn-primary" onClick="generateQRCode();">QRコード</button>
</div>

<canvas id="qrcode"></canvas>

<script>
var text = '';


function generateQRCode(){
  $('#qrcoe').html( '' );
  text = $('#text').val();
  if( text ){
    QRCode.toCanvas( document.getElementById( 'qrcode' ), text, function( err ){
      if( err ){
        console.log( err );
      }
    });
  }
}

  :

まず QR コードを描画するための <canvas> タグ(id="qrcode")を用意します。そしてボタンがクリックされるとテキストフィールドの値を取り出し、<canvas> の id を指定して QRCode を描画する、というシンプルなものです。これだけで QR コード化が実現できます。

文字列や URL を読み取らせたい対象者がスマートフォンユーザーだけであればこれだけで最小限必要な機能は実現できます。問題は対象者の中に PC ユーザーがいて、PC ユーザーにもこの URL や文字列を提供したい場合です。PC からは QR コードを読み取れない人が多数だと思うので直接メッセンジャーやメールなどで文字列や URL を送ればいいのですが、では逆に QR コードで表示されている文字列を取り出す便利な方法はないでしょうか? 上記サンプルの場合は QR コード化する文字列をテキストフィールドに指定しているのでテキストフィールドから取り出せばいいのですが、そういうケースばかりではありません。 以下は「QR コード部分をクリックした時に、その文字列をクリックボードにコピーする」ためのコードを加えました。これによってクリップボードからペーストするだけでメッセンジャー等で送れるようになり、UI的にも楽だと思ったのでした。 加えて、QR コード部分をクリックした際に「クリック感」というか「押された感」が出るような、一瞬だけ影ができるような視覚効果を加えてみました。

最終的なコードはこのようになりました:
  :
<script src="./qrcode.min.js"></script>

<style type="text/css">
html, body{
  text-align: center;
  background-color: #fafafa;
  font-size: 20px;
  color: #333;
}
#qrcode:active{
  border: 1px solid #334c66;
  background-color: #69c;
  -webkit-box-shadow:inset 0px 0px 8px #334c66;
  -moz-box-shadow: inset 0px 0px 2px #3a6da0;
  box-shadow: 0px 0px 2px #3a6da0;
}
</style>
</head>
<body>

<div class="container">
  <input type="text" id="text" value=""/><button class="btn btn-primary" onClick="generateQRCode();">QRコード</button>
</div>

<canvas id="qrcode"></canvas>

<script>
var text = '';

$(function(){
  $('#qrcode').on( 'click', function( e ){
    copyClipboard( text );
  });
});

function generateQRCode(){
  $('#qrcoe').html( '' );
  text = $('#text').val();
  if( text ){
    QRCode.toCanvas( document.getElementById( 'qrcode' ), text, function( err ){
      if( err ){
        console.log( err );
      }
    });
  }
}

function copyClipboard( str ){
  //. 空 div と空 pre
  var tmp = document.createElement( 'div' );
  var pre = document.createElement( 'pre' );

  pre.style.webkitUserSelect = 'auto';
  pre.style.userSelect = 'auto';
  tmp.appendChild( pre ).textContent = str;

  //. 画面外へ
  var s = tmp.style;
  s.position = 'fixed';
  s.right = '200%';

  //. body に追加
  document.body.appendChild( tmp );
  document.getSelection().selectAllChildren( tmp );

  //. クリップボードにコピー
  var result = document.execCommand( "copy" );

  //. 要素削除
  document.body.removeChild( tmp );

  return result;
}
</script>

  :

QR コード化したテキストの内容を変数 text に保存しておき、id="qrcode" のキャンバスがクリックされた時に text の値をクリップボードにコピーする、という内容を加えています。また視覚効果としてクリックされた時だけ有効になる #qrcode:active 要素を定義し、影をつけるなど見た目に変化を加えています。


このページのトップヘ