- 投稿日:2020-03-01T23:36:47+09:00
CSS組版を目指して --原稿からPDF作成まで--
技術書典8で「お絵描きソフトをつくる本」という本を発行することにしました。そこで、流行りのCSS組版をやってみたのでログを残しておこうと思います。
CSS組版って
組版は製本をするための工程の1つで、原稿を作成した後に、文章、図などをレイアウトしていく作業です。InDesignなどのDTPソフトウェアを使ってソフトウェア上でレイアウトをしてくことがほとんどです。また、TeXなどのソフトウェアも組版ソフトウェアの1つです。
最近では、原稿をHTMLで作成し、そのレイアウトをCSSで指定する、CSS組版と言われる手法も出てきました。vivliostyleなどのOSSを使うと、簡単にCSS組版を利用することが出来ます。CSS組版どうやるの?
まずは、やってみた人達のドキュメントを真似するのが手っ取り早いです。
https://vivliostyle.org/ja/samples/
https://vivliostyle.github.io/vivliostyle_doc/ja/vivliostyle-user-group-vol1/shinyu/index.html
https://vivliostyle.github.io/vivliostyle_doc/ja/vivliostyle-user-group-vol1/yamasy/index.html今回のワークフローの備忘録
いくつか「???」と思ったこともあったので、自分のワークフローのログを残そうと思います。
原稿作成
文章と図の入力
今回は複数人で原稿を持ち寄るので、その時のフォーマットはWordにしました。Markdownでも良いのですが、ページの分量を把握しながら図の位置を入れ込んだり、表を書いたりするのにはやっぱりWYSIWYGは楽だったりします。が、Linuxしか持ってない私が使ったのはWord for Web。
…便利なんですが、ゼロから原稿を作るのには向いてないですね、これ。ということで、最終的にはWordの文書を
.docファイルで保存してから、Google Drive経由でGoogle Document+Google Slideに移行しました。
Google Slideは良いですね。
- 使いたいショートカットが一通り揃っています。
- 少し癖はありますが、普通に作図ツールとして使えます。 特に、Ctrl+ドラッグでオブジェクトの複製をするショートカットが使えるのはポイントが高いです。
- 各スライドをSVGとして出力できます。
- 画像で出力…まではよくある話ですが、SVGの出力は重宝します。
Google Documentは、感触としてはGoogle Slideほどのインパクトはないですが、やはり地味に良いです。
- Google DocumentはSlideのページをコピペできます。
- これ、Officeだとできて当り前じゃん…と思ってたんですが、Word for Webにはこの機能が無いんですよね……
- 取り込んだ画像をあとからトリミング出来ます。
- 地味に便利です。HTMLに出力すると「あっ」と気づくんですが、これ、CSSのフィルタに変換されるんですね。 なので、HTMLで読んだ画像を後から編集すると、その編集後の画像をトリミングした状態になります。後述する画像のSVG化で便利です。
ただ、デザインやレイアウトについて言えば、利用できるフォントが致命的に少なかったので、原稿を作る以上の作業をするのは諦めました。
原稿のHTML化
Google Documentで書いた文書をHTMLに変換します。これは、Google Documentの「ダウンロード>ウェブページ」でダウンロードすればOKです。このときにはzipファイルがダウンロードできます。
ダウンロードしたファイルはこのような構造になっています。<文書名>.zip +--<文書>.html +images +--image1.png +--image2.png +--image3.jpg ...HTMLと画像がエクスポートされます。Slideから入れ込んだプレゼンの図もPNGなどに変換されています。ただし、このHTMLファイルは、幾つかの欠点があります。
- UTF-8の文字列がエスケープされているので編集には向きません。
- 「1.1 H1のタイトル」と入力したものは
<ol><li><h1>H1のタイトル</h1></li></ol>と展開されてしまいます。- 改行を入れると、
<p><span></span></p>という何もない無駄な構造が大量に生成されます。チマチマと直すのが非常に面倒です。なので、次のようなスクリプトを作って、HTMLを比較的プレーンなものに書き換えました。
html2html.py#!/usr/bin/env python3 import sys import html from bs4 import BeautifulSoup with open(sys.argv[1],"r") as f: soup = BeautifulSoup(f.read(), 'html.parser') span = soup.select("p>span:empty") for i in span: i.extract() p = soup.select("p:empty") for i in p: i.extract() headers = ["h%d"%i for i in range(2, 5)] for h in headers: hs = soup.select("ol>li>%s"%h) for i in hs: s = i.find("span") s.replace_with(s.string) i.parent.parent.replace_with(i) print(soup.prettify())あと、各要素に
style="c<数字>"というstyleが入れられるのですが、これも不要なので消してしまっても良いかもしれません。画像のSVG化
Google Documentは画像を出力してくれてよいのですが、PNGの解像度がどうしても低くなってしまいます。
そこで、Google Slideから取り込んだ画像はより高解像度なSVGに置き換えます。
まずはスライドをSVGとしてダウンロードします。
次に、ダウンロードしたHTMLのimages内にある、スライドに対応する画像のファイル名(image<数字>.png)を探しておいて、先程ダウンロードしたSVGファイルをimage<数字>.svgにリネームします。
後は、HTMLファイルの画像リンクの".png"を".svg"に書き換えます。
画像の解像度がはっきり上がるのが分かるので良いのですが、PDFとして書き出した後に、レンダリングで時間がかかるようになります。一長一短ですね…ページ脚注の修正
Google Documentでページ脚注を入れてHTMLで出力すると、脚注の参照元に
[<数字>]というテキストを持つリンクが埋め込まれ、その参照先は文書の最後に<div>要素として記載されます。一方、CSS3(というかvivliostyle)では、脚注は文書の脚注の参照元に直接<span style="float: footnote">で埋め込んでおきます。今回はCSSセレクタを使った<span class="footnote">というタグを手動で埋め込みましたが、これもスクリプトで変換できると楽そうです。コードブロックの修正
これは手作業で直していきます。Google Documentでコードを認識する機能が無いから仕方なし。
素の文章で入力されていると<br/>の実装が<p><span>コード</span></p>とかに展開されているので悲惨です。
諦めて、まとめて<pre><code class="language-*">コード</code></pre>に変換したほうが良いです。数式の入力
綺麗な数式は良いですよね。ということで、MathML + MathJaxを使って数式を入力します。
MathMLは流石に数式専用の処理系ということもあって、行列とかも綺麗に書けます。
ただ、MathMLを直接手書きするのはかなり辛いです… なんとかならないもんでしょうか…CSS組版
いよいよCSSを使って組版です。vivliostyleのビューアを使ってCSSを少しずつ変えながら確認をしていきます。
すでにいろいろと公開されている資料を見ると分かることは分かるのですが、自分がやったことをサクッとまとめておきます。デフォルト設定
まずHTMLをvivliostyleのビューアで開いてから、紙面のサイズを設定(例えばA4など)して、その結果表示されるCSS Detailsを表示します。
次に、HTMLにインポートするCSSを作成し、そこに先程の設定をコピペします。common.css@page { size: A4; }この
common.cssをhtmlから読み込むようにすれば、次からは勝手にこの設定が適用されます。フォント
人によると思いますが、本文は明朝体、章や節のタイトルはゴシック体というのがしっくり来ます。Webフォントを使う、ということがよくされるようですが、面倒なのでシステムにフォントをインストールしました。この方がSVGとかでフォント指定するときに楽なので良いかもしれません。
作成したのがUbuntuだったので、インストールされている「Noto Serif」「Noto Sans」に加えて「Source Code Pro」、「超極細ゴシック」を使いました。common.css/* 地の文は明朝体 */ :root { font-size: 10.5pt; font-family: "Noto Serif", "serif"; } /* 文書の一番最初のタイトル */ h1.title { font-family: "Chogokuboso Gothic", "Noto Sans","sans-serif"; font-weight: 1000; text-align: center; font-size: 250%; } /* 章タイトルはセンター寄せで大きな明朝体 */ h1 { font-family: "Noto Serif", "serif"; font-weight: 800; text-align: center; } /* 節、項のタイトルはゴシック体 */ h2,h3,h4 { font-family: "Noto Sans", "sans-serif"; font-weight: 700; } /* コードとリンクはSource Code Pro */ code, pre, a { font-family: "Source Code Pro", "Courier New", "monospace"; }目次と章の構成
<nav role="doc-toc">...</nav>という構造があると、vivliostyleはその要素の子要素に含まれるリンクを辿って文書に付け加えてくれるようです。
章ごとに異なるHTMLを作成しておいて、index.html<nav id="toc" role="doc-toc"> <h2>目次</h2> <ul> <h3> お絵描きの国のアリス <author>しーげっち</author> </h3> <li><a href="doc1-chapter1.html">はじめに</a></li> <li><a href="doc1-chapter2.html">お絵描きソフトのプログラムアーキテクチャ</a></li> <li><a href="doc1-chapter3.html">お絵描きソフトを支える技術</a></li> ... </ul> </nav>という内容を記述します。このindex.htmlを処理すると、自動的に全ての文書が取り込まれて1冊の本が出来てくれます。
カラーとモノクロ
CSSの設定を3つのファイルに分けます。
- カラー版とモノクロ版に共通の装飾設定(margin、padding、border、fontなど)
- カラー版に固有の設定(要素の色、模様など)
- モノクロ版に固有の設定(グレースケールの色、画像の白黒化など)
このなかで、共通設定の部分を
common.cssに書き、固有の設定をそれぞれcolor.css、mono.cssに書きます。mono.css@media print { /* 画像はグレースケール化 */ img { -webkit-filter: grayscale(100%); filter: grayscale(100%); } /* リンクは黒テキスト化 */ a { color: black; } }それから、
common.cssにcommon.css@import url(./mono.css); /* カラーの場合は color.css */と書いておけば、適切な設定を読み込んでくれます。とても簡単です。
モノクロのときはハイコントラスト、カラーのときはいろいろな色を使う、といった使い分けが簡単に出来ます。改ページ
いくつかやり方はあると思いますが、私は
<hr>タグを使うことにしました。common.csshr.page-wrap { break-before: page; visibility: hidden; margin: 0px; padding: 0px; height: 1px; }としておいて、本文に
doc.htmlここまでが前のページ <hr class="page-wrap" /> ここからが次のページと書くと改ページされます。
脚注
脚注を作るときには、CSSの変数を利用します。テンプレだと思うのですが、サンプルから探すのが結構疲れたので、抜き出しておきます。
doc.html本文中に脚注<span class="footnote">これが脚注の本体</span>を書きます。CSSには脚注本体と、脚注へのリンクを示すマーカーの両方の記述を書きます。
common.csshtml { counter-reset: footnote; } @media print { /* 脚注本体の書式 */ .footnote { float: footnote; /* CSSでページ下部に脚注を置くための設定 */ font-size: 8pt; /* フォントは小さくしたい */ counter-increment: footnote; text-indent: 0; } /* 脚注本体の行頭に[<数字>]を挿入する */ .footnote::footnote-marker { content: "[" counter(footnote) "]"; font-size: 8pt; display: inline; vertical-align: super; } /* 脚注の参照元にリンクを作成する */ .footnote::footnote-call { content: "[" counter(footnote) "]"; font-size: 8pt; vertical-align: super; display: inline; line-height: 1; } }こうすると脚注は出来るのですが、
index.htmlから章ごとの文章を取り込むとき、各章ごとで番号がリセットされるので通番を付けることは出来ませんでした。だれか、文書全体を通して通番で採番する方法を知っていたら教えてください…ヘッダとフッタ
各ページの上部に文書や章のタイトル、下部にページ番号を表示、というのはよくあります。
更に、奇数ページと偶数ページでレイアウトを変えたいこともよくあります。(例えばページ番号を真ん中ではなく、ページの外側に表示させたいときなど)
vivliostyleのユーザグループで作られたサンプルを参照すると分かりやすいです。common.css/* 横書きの場合の左ページ */ @page:verso { @top-left { /* ページヘッダ(左) */ font-family: "Noto Sans", "sans-serif"; content: env(pub-title); /*書名=(index.html)の<title>要素を記載*/ } @bottom-left { /* ページフッタ(左) */ content: counter(page); /* ページ番号 */ } } /* 横書きの場合の右ページ */ @page:recto { @top-right { /* ページヘッダ(右) */ font-family: "Noto Sans", "sans-serif"; content: env(doc-title); /* このhtmlの<title>を表示 */ } @bottom-right { /* ページフッタ(右) */ content: counter(page); /* ページ番号 */ } }ソースコードのハイライト
highlight.jsとかprism.jsとかを使えばいいんだと思いますが、今回、何故か上手く出来ませんでした。HTMLをそのままブラウザで開くと反映されるんですが、vivliostyle viewerを経由すると上手く出来ませんでした。なぜ…?
その他困ったこと
vivliostyleのビューアが、突然HTMLを更新してくれなくなってしまうことがありました。しばらく放置してから読み込むと上手く読めることもあったので、何かのタイムスタンプ周りの問題が起こってるんだと思いますが…
SMBを使ってファイル共有していたりしていたので、その辺りが問題かもしれません。電子版PDFの作成
これでいいか、と思ったらPDFを作ります。最近はvivliostyleコマンドが多機能になっているようなので、次のようにすれば良いです。
build.shvivliostyle build -o doc.pdf -b index.html実行すると、裏でHeadless Chromiumが起動して、しばらくするとPDFが出来上がります。
入稿用PDFの作成
さて、これで終わらないところがCSSの組版の辛いところ…
CSS組版の光と闇を見てもらうと分かるのですが、vivliostyle(というかChromium)が生成するPDFには「Type3フォント」が大量に埋め込まれ、これが印刷所では処理できないことが多々あります。(実際、Inkscapeで読み込めなかったりします。)
上のリンクの記事ではInDesignか、MacOSのpreview.appを使う、ということになっていましたが、そんな環境は持っていません…
困った…とTwitterでつぶやいたところ、解決方法(になるはずのもの)を教えてもらいました。ありがたいことです。なんと、GhostScriptを使うのだそうです。確かにPDFハンドリングできますよね……
pdf-outline-font.shDOC=$1 DOC=${DOC:-doc.pdf} gs -dNOPAUSE -dBATCH -dNoOutputFonts -sDEVICE=pdfwrite -o outline-$DOC -f $DOCこの方法だと、フォントは全てパスとして展開されるので、テキスト情報は失われます。ただ、入稿用のPDFとしては確実なんじゃないかと思います。
もっとも、今回やむを得ない事情により技術書典8が中止になってしまいましたので、この方法が印刷書で問題なく印刷できるのか、確認は出来ていません。論理的には問題ないはずですが。
表紙の合成
入稿用のPDFなら、表紙は別ファイルで管理すれば良いかもしれません。ただ、電子版だと表紙を合成したくなります。
複数のPDFを合成してくれるプログラムは無いかな−と思っていくつか見ていましたが、それよりもPyPDF2パッケージを使って自分でスクリプトを書いたほうが早い、という結論になりました。bookbinding.py#!/usr/bin/env python3 import sys import PyPDF2 output_name = sys.argv[1] # 出力するPDFファイル名 front_cover = sys.argv[2] # 表の表紙のPDFファイル名 inner_cover = sys.argv[3] # 内表紙のPDFファイル名 book_body = sys.argv[4] # 文書本体のPDFファイル名 back_cover = sys.argv[5] # 裏の表紙のPDFファイル名 with open(front_cover, mode='rb') as fc, \ open(inner_cover, mode='rb') as ic, \ open(book_body, mode='rb') as bb, \ open(back_cover, mode='rb') as bc: front_reader = PyPDF2.PdfFileReader(fc) inner_reader = PyPDF2.PdfFileReader(ic) body_reader = PyPDF2.PdfFileReader(bb) back_reader = PyPDF2.PdfFileReader(bc) writer = PyPDF2.PdfFileWriter() fc_page = front_reader.getPage(0) print("Add Front Cover %s"%(front_cover)) writer.addPage(fc_page) writer.addBlankPage() ic_page = inner_reader.getPage(0) print("Add Inner Cover %s"%(inner_cover)) writer.addPage(ic_page) for i in range(1, body_reader.numPages): page = body_reader.getPage(i) print("Add page %d"%(i+1)) writer.addPage(page) if body_reader.numPages % 2: writer.addBlankPage() bc_page = back_reader.getPage(0) print("Add Back Cover %s"%(back_cover)) writer.addBlankPage() writer.addPage(bc_page) with open(output_name, mode='wb') as output: writer.write(output)これで電子版のファイルが出来ました。
やってみて
CSSを知っていれば、割と簡単にページのレイアウトが出来るのは良いですね。ただ、やっぱり「CSSのmedia仕様はよく知らない…」とかでググらなければならないことは出てきます。原稿をギリギリまで書いていて、最後に組版をしようとして思わぬところで足をすくわれかねない気はしました。
一度CSS組版の基礎を確立してしまえば、2回目からはMarkdown→HTML→細かく修正でもなんとかなりそうな気はしました。ただ、図表に関して言えばWYSIWYGエディタのほうが圧倒的なので、何を重視するかで決まるのかもしれません。
CSS組版をやるときは、一度自分なりのワークフローを確立するのが大切ですね。余談
今回、同人誌を作ってわかったのですが、このくらいの分量の文章だと、あっという間にA4数十ページの記事になってしまうんですね…
びっくりです。
- 投稿日:2020-03-01T22:51:10+09:00
背景色によって文字色が変わる、流れる文字のCSSアニメーション実現方法
はじめに
コード
index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" /> <title>流れる単語</title> <link rel="stylesheet" href="./index.css" /> </head> <body> <div class="color-area"> <div class="scroll-string scroll-string-white">流れる単語</div> </div> <div class="scroll-string scroll-string-blue">流れる単語</div> </body> </html>index.csshtml { height: 100%; } body { height: 100%; } .color-area { width: 100%; height: 200px; top: 100px; position: relative; overflow: hidden; background-color: darkblue; z-index: 2; } .scroll-string { font-size: 30px; visibility: hidden; text-align: center; height: 50px; width: 100%; position: absolute; left: 0; right: 0; margin: auto; } .scroll-string-white { color: white; top: 0; animation: scroll-white 5s linear 0s 1 forwards; -webkit-animation: scroll-white 5s linear 0s 1 forwards; z-index: 3; } .scroll-string-blue { color: darkblue; top: 100; animation: scroll-blue 5s linear 0s 1 forwards; -webkit-animation: scroll-blue 5s linear 0s 1 forwards; z-index: 1; } @keyframes scroll-white { 0% { transform: translateY(-100px); visibility: visible; } 100% { transform: translateY(300px); } } @keyframes scroll-blue { 0% { transform: translateY(-200px); visibility: visible; opacity: 0; } 10% { opacity: 1; } 90% { opacity: 1; } 100% { opacity: 0; transform: translateY(200px); } }ポイント
白い文字と青い文字を重ねて同時にアニメーションさせています。レイヤー構造は上から順に
- 白い文字
- 青い背景
- 青い文字
- 白い背景
となっています。その際、青い背景を
overflow: hiddenとすることで白い文字を青い背景の上のみで表示させます。おわりに
もっと効率的の良い書き方があるかもしれません。
- 投稿日:2020-03-01T22:10:22+09:00
【最新版】Tinderで自動右スワイプできなくなった難民を救うコード
はじめに
2020年3月1日現在、Tinderのいいねボタンを含むコンポーネントのクラス名が変わった為、以前と同じように自動スワイプできなくなる問題が発生しました。これは昨今流行している「新型コロナウィルス」に勝るとも劣らない、日本社会における文化的な交流を妨げる由々しき事態と考え、急ぎ解決策を書き起こした次第です。
コード
御託はいいからさっさと教えろって方、お待たせしました('ω')
『一度LIKEを手動でクリックしてから』
F12でDeveloper Toolを開き、下記のコードをConsoleにコピペしてEnterを叩けば新たな出会いが開かれるでしょう。
ブラウザを更新しなければ、検索距離や年齢等の条件を変更も可能で、再度の手動クリックは不要です。
Tinder.jscount = setInterval( function(){ var buttons = document.getElementsByClassName("button Lts($ls-s) Z(0) CenterAlign Mx(a) Cur(p) Tt(u) Bdrs(50%) P(0) Fw($semibold) focus-button-style Bxsh($bxsh-btn) Expand D(b) Bgc(#fff) Trstf(e) Trsdu($normal) Wc($transform) Scale(1.1):h Scale(.9):a"); buttons[2].click() },1000)コード解説(開発者向け)
setInterval - 処理を定期実行するメソッド(第一引数:処理内容、第二引数:処理のインターバル(ミリ秒))
getElementsByClassName - クラス名を指定して、そのクラスが持つメソッドなんかを丸々取得
buttons - 4つのボタンのリスト(Dislike, Super Like, Like, Boost)3番目がLike
※上述しましたが、手動クリック前後で各ボタンの挙動が変わる場合がある為注意。おわりに
参考になった方は右スワイプ、もとい、いいねをお願いします!
- 投稿日:2020-03-01T20:13:13+09:00
VueとCSSとTypeScriptでシューティングゲーム「ネコメザシアタック2020」を作ったのでソースと解説
こんにちは
今日は2/22の猫の日に合わせて個人開発したゲーム「ネコメザシアタック」の技術的なポイントを解説する記事です。去年のバージョンはこちら
作ったもの
ソース: https://github.com/yuneco/mezashi2
アプリ: https://nekomzs2.web.app/
(PCでも遊べるけどスマホ推薦です)使っている技術
そろそろリリース見込みのVue3を先取りした構成です
- Vue(Vue2 + CompositionAPI)
- TypeScript
- CSS Transition(ほとんどのアニメーション)
- SVG(画像 + 一部のアニメーション)
- Firebase (Hosting + FireStoreでランキング)
おしながき(この記事の内容)
作ったもの全部を解説していくとキリがないので、主に去年からの差分を中心に面白いポイントだけ説明していきます。
アニメーションのポイント
角丸の地面を歩くアニメーション
星から星に飛び移るアニメーション
CSS Transitionでゲームを作るときの悩みと解決法
アニメーションの途中で現在の位置や角度をどうやって取得するか
新しい技術スタックのポイント
TypeScript + CompositionAPIの採用
それでは行ってみましょう
![]()
ポイント1: 角丸の地面を歩くアニメーション
まず今回の目玉である「たまさんが角丸の星の上をぴょんぴょん跳ねたり歩いたりする表現」を作っていきましょう。(※たまさんはこのゲームのメインキャラクターです)
長方形の上を歩く
いきなり角丸は難しそうなので、ひとまず星はただの長方形にしました。こんな感じでパラメーター
tamaXを渡すといい感じにコーナーリングしてくれるようにしてみます。たまさんコンポーネント<TamaSan :tamaX="1.39" />この計算は単純だけど面倒なので、独立した関数にしておきます。
Angle8.tsimport Pos from './Pos' // x, y, r(角度)をセットで保持する値クラス export default { /** * 指定したX位置(一周=8)に対応する座標・回転角を求めます * @param val X位置 * @param gw 地面の幅 * @param gh 地面の高さ */ at: (val: number, gw: number, gh: number): Pos => { const segIndex = Math.floor(val) const prog = val - segIndex const turnsR = Math.floor(val / 8) * 360 switch (segIndex % 8) { case 0: return new Pos(gw * prog, 0, 0 + turnsR) case 1: return new Pos(gw, 0, prog * 90 + turnsR) case 2: return new Pos(gw, gh * prog, 90 + turnsR) case 3: return new Pos(gw, gh, 90 + prog * 90 + turnsR) case 4: return new Pos(gw * (1 - prog), gh, 180 + turnsR) case 5: return new Pos(0, gh, 180 + prog * 90 + turnsR) case 6: return new Pos(0, gh * (1 - prog), 270 + turnsR) default: return new Pos(0, 0, 270 + prog * 90 + turnsR) } } }これで
TamaSanコンポーネントではTamaSan.vueconst tamaPos = computed<Pos>(() => Angle8.at(props.tamaX, ground.w, ground.h))こんな感じで算出プロパティとして簡単に位置と角度を取得できます。
角丸を歩けるようにする
つづけてこの長方形の角をとって角丸にしていきます。
一見難しそうに思える角丸ですが、実は種明かしをするとすごく簡単。「たまさんのキャラクター本体を角丸のコーナーサイズと同じだけ宙に浮かせているだけ」です角丸に限らず、惑星の公転のような円運動は全て同様の理屈で単純な回転角の変更のみで表現できます。覚えておくといろんなものをくるくる回せて楽しいですよ
![]()
ポイント2: 星から星に飛び移るアニメーション
今回のゲームでは角丸の星を一周するごとに次の星に飛び移ってゲームが進んでいきます。
一見するとこれも複雑なアニメーションを計算しているように思えますが、実は簡単なCSS Transitionのみで実現しています。コードより前に動画を見てみると仕掛けがわかります。
そう、実はたまさんは星から星に飛び移っていたのではなく、土台の長方形が移動していただけ(たまさんはその場でジャンプしていただけ)だったのです。
わかってしまえば簡単ですね。コードで見てみるとこんな感じ:
テンプレート部分<!-- たまさんの土台(中にたまさん本体もいる) --> <TamaHome :tamaX="tamaHomeState.tamaX / 100" :groundPos="activePlanet.pos" :groundSize="activePlanet.size" :groundRound="activePlanet.round" /> <!-- 惑星1 --> <Planet :round="planet1State.round" :pos="planet1State.pos" :size="planet1State.size" /> <!-- 惑星2 --> <Planet :round="planet2State.round" :pos="planet2State.pos" :size="planet2State.size" />2つの惑星とたまさん(の土台。中にたまさん本体も入ってる)が並列に並んでいます。
たまさんの土台は、activePlanet(後述)の位置・角度・サイズに合わせていることに注目してください。スクリプト部分も見てみます:
スクリプト部分setup () { /** たまさん(土台)の状態 */ const tamaHomeState = reactive<TamaHomeState>({ tamaX: 0, planetIndex: 0 // 乗っている惑星のindex }) const planet1State = /* 略:惑星1の位置・角度・サイズ */ const planet1State = /* 略:惑星2の位置・角度・サイズ */ /** planetIndexの値によって惑星1か惑星2のどちらかを返す */ const activePlanet = computed<PlanetState>(() => tamaHomeState.planetIndex % 2 === 0 ? planet1State : planet2State ) // ...略 }たまさんの土台の位置を決める
activePlanetはcomputedを使って惑星1か惑星2のどちらかを返すようにしています。ボタンを押すたびにこのactivePlanetが切り替わることで、隣の星に飛び移る(かのような)アニメーションを表現することができるのです。おまけ:パフォーマンス戦略
先ほどの動画の右側にVueのプロパティを表示してみました。
位置やサイズの値が書き変わるのはボタンをクリックした瞬間の一度だけなのがわかるかと思います。Tweenライブラリを使ったり、一コマごとに座標を計算してプロパティを変更すればより柔軟なアニメーションを作れますが、その分パフォーマンスは大きく低下します。CSS Transitionで実現できる部分はできるだけまかせて、JavaScript側の処理を減らしてあげると滑らかなアニメーションを実現できます。1
ポイント3:アニメーションの途中で現在の位置や角度をどうやって取得するか
上記したように、アニメーションをTweenやコマ計算ではなく、できるだけCSS Transitionに任せていくのがパフォーマンス向上の重要な戦略です。その一方で途中のアニメーションを全てCSSに任せてしまうとゲームとしては困ったこと
もでてきます。
今回の場合、
「タップした瞬間にカツオをタップした方に向け、メザシを発射する」
という部分。
これを実現するにはアニメーションの途中であっても、タップしたその瞬間の位置・角度を取得する必要があります。しかし残念なことに、CSS Transitionで変化している途中のプロパティを直接取得する方法がありません
。 0.5秒後にDOMから直接
style.transformを取得してもトランジション終了後の値であるtransitionX(500px) rotate(30deg)しか取得できないのです。任意の時点の位置を取得する
まずは位置からです。
位置の取得は実は去年、当たり判定の処理を作る中でもやっています。具体的にはElement.getBoundingClientRect()を使ってピューポート上での位置を求めればOK。TamaSan.vueconst getTamaPos = (): Pos | null => { // テンプレート内のDOMを取得 // ※Vue2のthis.$refs('tamaBody')と同じ // この部分は後ろの節でも解説しています const tamaBody = tamaBody.value // div要素 if (!tamaBody) { return null } const p = tamaBody.getBoundingClientRect() return new Pos(p.x + p.width / 2, p.y + p.height / 2, 0) }クリックされた時点でたまさんの本体が入っている要素の表示領域(BoundingClientRect)を取得し、その中心を現在の位置として返しています。
角度も取得する
先ほどの
getTamaPosメソットでは角度を0で返してしまっていましたが、カツオをタップした方に向けるためには、今たまさんがどっちを向いているのかを知る必要があります。角度も取得するようにしましょう。あいにく、
Element.getBoundingClientRectでは位置を知ることはできても角度はわかりません。これは、getBoundingClientRectがあくまで画面描画において要素がどこに描画されるかを求める機能しか持たないためです。位置のみを使って角度を求めるため、たまさんのなかに2つの小さなdivを置き、この2つの位置関係から角度を求めることにします。こんな感じでたまさんの中に2つのDiv要素を配置します
TamaSan.vue<div class="pos-detector"> <div class="detector-top" ref="detTop"></div> <div class="detector-bottom" ref="detBottom"></div> </div>2つDivを作って
TamaSan.vue<style lang="scss" scoped> .pos-detector { position: absolute; width: 0px; height: 100px; top: calc(50% - 50px); left: 50%; div { position: absolute; width: 1px; height: 1px; } .detector-top { top: 0; } .detector-bottom { bottom: 0; } }たまさん中央に縦に並べるだけ。
あとはこの2つのDivの位置から角度を計算します。先ほどのgetTamaPosメソッドに角度を求める処理を追加します。TamaSan.vueconst detTop = ref<HTMLDivElement>(null) // Vue2の this.$refs.detTop の宣言 const detBottom = ref<HTMLDivElement>(null) // 同上 const getTamaPos = (): Pos | null => { const elTop = detTop.value const elBtm = detBottom.value if (!elTop || !elBtm) { return null } const pTop = elTop.getBoundingClientRect() // 上側の位置を取得 const pBtm = elBtm.getBoundingClientRect() // 下側の位置を取得 const cx = (pTop.x + pBtm.x) / 2 // 中心X const cy = (pTop.y + pBtm.y) / 2 // 中心Y const rad2ang = (rad: number) => rad / Math.PI * 180 // ラジアン→角度の変換関数 const r = rad2ang(Math.atan2((pBtm.y - pTop.y), (pBtm.x - pTop.x))) // Math.atan2で角度を求める return new Pos(cx, cy, r) }「2点の座標がわかれば回転角を簡単に求められる」というのは覚えておいて損のない知識かと思います。CSSアニメーションの文脈で使うことは滅多にないと思いますが、ゲームやビジュアル表現ではよく使う計算です。
ポイント4:TypeScript + CompositionAPIの採用
この節はコードばっかりなので興味ない方は飛ばしつつ見てくださいませ
![]()
冒頭でも書いた通り、今回はもうすぐやってくるVue3を見据えて、CompositionAPI + TypeScriptの構成に挑戦しています。CompositionAPI + TypeScriptで何が変わるの?って部分は以前の記事を見てみてください。従来の書き方との対応がわかりやすいかと思います。
Vue.jsレベルを上げよう!○×ゲームを作ってTypeScript&Vue3のCompositionAPIと仲良くなる
ここでは、基本のCompositionAPI + TypeScriptは理解した上で、つまづきポイントと解決策を共有します。
$refs(テンプレートRef)どこいった問題
テンプレートRefは以下のようにして
template部分で指定した要素や子コンポーネントを参照する機能です。Vue2標準のテンプレートRef<template> <div> <button @click="getSpan">Get ref</button> <span ref="msg">Hello</span> </div> </template> <script> export default { methods: { // this.$refsでテンプレート内のSpan要素を取得できる getSpan () { console.log(this.$refs('msg')) } } } </script>CompositionAPIでは
refを使います。名前は似てるけど使い方はだいぶん違うので注意CompositionAPIのテンプレートRef<template> <!-- 同じなので省略 --> </template> <script lang="ts"> import { createComponent, ref } from '@vue/composition-api' export default createComponent({ setup () { const msg = ref() // 中身のないrefを作る。※重要なのは名前※ // msg.valueでテンプレート内の要素にアクセスできるようになる const getSpan = () => { console.log(msg.value) } return { msg, getSpan } } }) </script>紛らわしいのが、この
refは基本的には従来の$dataを代替するものなのに、なぜかテンプレートRefの機能も兼ねているところ。RFCの解説にRefの説明はあるのですが、これを読んでもいまいちテンプレートRefについては理解できないのでは?という気がします....テンプレートRefの型を決めたい(HTMLElement編)
なんとかテンプレートの要素にアクセスできたところで、次に問題なるのはTypeScriptの型問題です。このままだと
msg.valueのようにして取得した要素をspanとして扱えないのでちょっと嫌ですよね。前項までのはなしは一応公式にサンプルも書かれているのですが、これ、JSですね...
https://vue-composition-api-rfc.netlify.com/api.html#template-refsTSでのやり方がなぜか見つからないのですが、一応、下記のようにすれば型を明示することができます。
テンプレートRefの型を明示する<script lang="ts"> ... 略 ... const msg = ref<HTMLSpanElement>() // 型を明示してrefを作る const getSpan = () => { const msgSpan = msg.value // HTMLSpanElementとして取得できる } ... 略 ... </script>テンプレートRefで子コンポーネントにアクセスしたいんだってば
![]()
OK、普通のSpanやDivならなんとかなった。じゃあコンポーネントだと?
↓これでいけそうな気がするじゃないですか?子コンポーネントにアクセス(ダメな例)<template> <div> <TamaSan ref="tamaRef" /><!-- このたまさんにアクセスしたい --> </div> </template> <script lang="ts"> import { createComponent, ref } from '@vue/composition-api' import TamaSan from './TamaSan.vue' // たまさんコンポーネント読み込み export default createComponent({ components: { TamaSan }, setup () { const tamaRef = ref<TamaSan>() // TamaSan型 return { tamaRef } } }) </script>怒られます
。「TamaSanは型ではなく値なので、型を指定しろ」とのお言葉。以下のようにするとうまくいきます:
子コンポーネントにアクセス(うまくいく例1)const tamaRef = ref<InstanceType<typeof TamaSan>>()TS分かんね
ってなるやつですね。
TS初心者の私はこれ見つけるまでにStackOverflowを2時間くらいさまよいました。(そしてリンク失念しました...ごめんなさい)
また、コンポーネント固有のデータやメソッドは不要で、単にVueのコンポーネントとして扱いたいだけであれば、以下のようにすることもできます:
子コンポーネントにアクセス(うまくいく例2)const tamaRef = ref<Vue>()(ちなみにこの書き方だと従来の
$refsや$elにアクセスすることもできます)うん、複雑。。
しかもこのあたりの型は別に自動的に判別してくれているわけではなく、あくまでも宣言に従って型を当てはめてくれているにすぎません。ちゃんと宣言すればエディタ上での作業は快適になりますが、宣言を誤ればそのまま実行時エラーなので、あまり安全とは言えない気がします。このあたりはまだまだVue + TypeScriptの辛いところだなぁ...というのが正直な感想です。。
まとめ
そんなわけで今年も気合いで新ゲームをリリースすることができました
去年一年ことあるごとにVueでゲーム作るの楽しいよ!!!って言い続けてるのですが、イマイチまだ流れが来ていない気がします。
もっとみんなVueで遊ぼう
この記事では駆け足で流してしまった部分も、過去にいくつか解説している記事があるので、よろしければご参照くださいませ:
特にスペックの低い旧機種のiPhoneではこの恩恵が大きく出ます。今回のゲームの場合、iPhone6レベルでも一度アニメーションを開始してしまえばコマ落ちをほぼ感じずにプレイすることができます。このあたりは以前の記事will-changeで目指す60fpsのぬるぬるCSSアニメーションをご参照くださいませ。 ↩
- 投稿日:2020-03-01T19:40:50+09:00
めくるめく粗挽きWebブラウザエンジンの世界
2020 Web Milestones という記事で知ったんだけどflow( https://www.ekioh.com/flow-browser/ )というブラウザエンジンがclean-room実装で熱いらしい。まぁ実際にはHTMLベースのUIエンジンで消えた商用実装なんて一杯あるし...と思っていたが 開発blogがめっちゃ面白い 。
内部精度を16.16固定小数点にしていたら互換性チェックに撥ねられた とかUIエンジンあるあるだし、 GMailを表示できる のは地味にすごい。 開発者インタビューも興味深く 、ついでに インタビューされているco-founderのTwitter も面白い。
We chose Spidermonkey in 2006,
...意外と開発期間長いね。。(Servoより古いことになる)
というわけで、 KHTML 由来でないWebブラウザエンジン特集。
消えたHTML4 ソリューション達
現在のHTML5の世界では、KHTML → WebKit / Blink の世界征服はほぼ完了したと言って良い状況だが、HTML4時代には先のインタビューにも
Back before we started our SVG engine, there were many HTML 4 browser engines for the TV market, such as ANT Fresco and Galio (which I also worked on), Access’s NetFront, Oregan, Espial and Opera.
と言及されているように、それなりの数の製品が市場には存在した。
Fresco とその後継である Galio 、 Oregan Media Browser はいずれもいわゆるIPセットボックス、つまりTVに接続されるメディア端末をメインターゲットとしたUIエンジンとして販売されていた。これらの製品は現存しない。
NetFront と Opera はそれぞれ比較的高機能なWebブラウザエンジンを実装していて、上記のブラウザと同様の組込みマーケットの他にPC/PDA上でも動作する製品を出していた。特にOperaはPC上での展開の方が有名かもしれない。これらのブラウザと Espiral は、後にWebKit/Blinkベースのブラウザに転向している。
これらのうち現行製品として(WebKit以外のソリューションが)生きているのはNetFrontだけで、 paneEとリブランドされた 後、NetFront Console という製品になっている。
ここで言及されていないものとしては、ガラケー時代のモバイルブラウザ、例えばibisBrowser等が存在した。
現行製品の比較
じゃぁHTML5の時代はどうなっているのかというと、まず、↑で挙げたようなブラウザはWebKit/Blink移植としてサービスを依然展開しているところもある。また、KHTML由来であるこれらはLGPLなので、他にも独自のWebKit移植はいくつか見られる。
- Chromium Embedded Framework(CEF) https://bitbucket.org/chromiumembedded/cef/src/master/ がこの界隈ではもっとも有名だろう。AdobeのAcrobatのようなアプリケーションに内蔵される他、SpotifyやAmazon MusicのようなオンラインサービスのPC版でも使用されている。
- EAWebKit https://gpl.ea.com/eawebkit.html ゲーム内蔵のための機能を備えたWebKitで、EAのアカウントサービス等との統合に使用される。
等。
要は、ElectronやNW.jsに代表されるような、 "HTMLでアプリケーションを書いてアプリケーションということにしたい" という需要自体は組込み分野でもそれなりに存在している。
組込みブラウザキット Cobalt
従来のHTML4ソリューションがターゲットしてきた、いわゆるセットトップボックスにこれだけのマーケットがあるのにオープンソースのソリューションが無いのかというと実は存在して、GoogleがYouTube TVの移植層として提供しているCobalt( https://cobalt.foo/ )がそれに当たる。Cobaltの名前は例えばPS4のYouTubeアプリのバージョン情報に確認できる。
Cobaltはフルスクリーンアプリ限定に機能を絞った独自レイアウトエンジンとV8 JavaScriptエンジンを組み合わせて、主にゲーム機上でのWebGL実装を想定したOpenGL ES2移植レイヤを備えている。
Flowは見たところCobaltよりも高機能だが、現状のYouTube以上の機能性を必要としているマーケットがどの程度あるかは難しいところではないかと思う。つまり、ストリーミングソリューションのベンダ側がCobaltをターゲットにした開発ノウハウを蓄積してしまうと、Flowのようなブラウザには席が無くなってしまうのではないかという懸念がある。
CobaltがサポートしているHTMLタグはかなり少い が、メディアコンテナ類は当然の権利のように充実したサポートを誇っている。
ネイティブソリューション
そもそも、素のHTMLを手書きする人口自体もそこまで多くなく、もっと高レベルなワークフローにターゲットして提供されるソリューションもある。
You.I TVのEngine One( https://www.youi.tv/ )はReact + After Effectsの開発を想定したフレームワークで、要するに オンラインストリーミングサービスの開発に特化したReactNative と言える。... 要は、ReactNativeがあるのにフルのHTML実装なんか必要なのかという問題が商用ブラウザにはたちはだかることになる。
ネイティブソリューションはプラットフォームの制約に左右されやすい弱点があるが、Engine Oneはネイティブコードを事実上動かせないRokuでサービスを提供するために シーングラフ構築をクラウド側にやらせる という とんでもない力技 でこれを解決している。
ゲーム向け(DOM less)
先のEAWebKitのようにゲームに内蔵されるブラウザもあるが、逆に、ブラウザゲームを動かすためのブラウザという枠も一応存在する。
(エマージングプロダクトの警告: ここから下はかなり粗挽きになっているのに注意。)
Ejectra
- https://impactjs.com/ejecta - 公式サイト
- https://github.com/phoboslab/Ejecta - リポジトリ
Ejectraは既にアクティブに開発されてはいないものの依然興味深いプロジェクトで、元々はHTML5ゲームエンジンであるImpact( https://github.com/phoboslab/impact )のゲームをiOS上でネイティブ動作させるために開発された。iOS以外のプラットフォームには対応していない。
ブラウザというか、表示できるのは
<canvas>の2d/3d(WebGL)コンテキストだけという潔さでこのelementもHTMLでは作成できずエンジン側が生成する。JavaScriptの実行はiOS提供のJavaScriptCore。入力は各種windowイベントやモーションに対応。また部分的にWebAudioを実装している。EjectraはWebGLを実装しているため、three.jsのような他のエンジンで実装されたゲームもホストできる。
BabylonNative
MSが開発しているBabylonNativeは、同社のHTML5ゲームエンジンであるBabylon.jsのコンテンツを動作させることに特化した実装で、Win32、UWP、iOSをサポートしている。Android実装は途上。
こちらはBabylon.js自体のレンダリングパイプラインを置き換えて動作するためWebGLは実装しておらず、クロスプラットフォームのレンダリングAPIであるbgfx( https://github.com/bkaradzic/bgfx )とKhronosのGLSL実装の上に構築されている。
また、JavaScriptエンジンを置き換え可能にするため、Node.jsの抽象化層であるN-APIを一部採用している。...もっともMS Edgeの現状を考えるとChakraがどうなるのかは何も言えないが。。
WebAPI実装はXMLHttpRequestのみで、入力やアセットのロードなど他の機能は全てBabylon.js側の抽象化層に実装している。
HTML向けのもの
Web上にはいくつかCSSに対応したレイアウトエンジンの実装を目指したプロジェクトがある。日本語圏だけでも:
- https://github.com/esrille/escudo Escudoは ES Operating System の流れで開発開始されたWebエンジンだが 開発blogは 2013年の更新 が最後になっている
- https://github.com/maekawatoshiki/naglfar NaglfarはRustで書かれたWebブラウザエンジンで、パーサについては Qiitaにも記事がある
などが有り、世間的にもかなりの量がある。
最も完成度が高いのは NetSurf (http://www.netsurf-browser.org/) だと思うが、それでもDOMに関してはかなり途上で、かつ、非latinのレンダリングは壊滅的になっている。NetSurfは C で実装されたHTML5パーサなどの部品を提供している。
Litehtml ( https://github.com/litehtml/litehtml ) はC++実装のレイアウトエンジンで、Googleのgumbo HTML5パーサ( https://github.com/google/gumbo-parser ) をHTML5パーサとして採用している。少くとも公式サイト( http://www.litehtml.com/ )は正常にレンダリングできる。
litehtml (https://t.co/0C0XnjjfBY) + imgui render hooks + lumix glue+net code (https://t.co/TiYppPnLhp) = basic web renderer @mikulasflorek pic.twitter.com/wV5K7Iuhyv
— Omar (@ocornut) July 26, 2017Litehtmlは(CSSフレームワークの)Bootstrapで書かれたサイトや↑のtweetのようにHackerNewsをレンダリングできる程度の互換性はあり、NetSurfよりもコンパクトな実装も相俟って興味深い。
Lexbor ( https://github.com/lexbor/lexbor ) はもっと野心的でNetSurfのように関連ライブラリもフルスクラッチで実装しているが、NetSurfやLitehtmlと異なりレイアウトエンジンをまだ備えていないためブラウザとしてはまだ使えない。
かんそう
残念ながら現状はFlowのようなプロダクトが生き残るには大変厳しい環境と言わざるを得ないが、どういうビジネスを今後展開していくのかは興味深い。
WebKit/Blink以外のブラウザが今後生きる道があるかというのも微妙な問題だが、 以前のエントリでNode.js + jsdomにUnityのゲームを載せた ように Webプラットフォーム上で書かれたアプリを動作させるための特化環境 はそれなりにチャンスが有るんじゃないかという気がしている。
ReactNativeに代表されるような、"DOM less" Webプラットフォームが隆興している現状を考えると、traditionalで"完全"なWebブラウザよりも、部分的でも現実的なワークロードを提供できるレベルのものに割と需要が有るのではないだろうか。
- 投稿日:2020-03-01T17:31:51+09:00
製薬企業研究者がPHPの基本的な文法についてざっくりまとめてみた
はじめに
ここでは、PHPの基本的な文法をざっくりとまとめてみます。
基本的な記述方法
PHPは
.phpという拡張子のファイルに記述します。
HTMLのソースコードの中で<?phpから始まる部分がPHPのコード部分になります。
PHPでは文末にセミコロン;が必要になります。
コメントを書くときは、//を使います。
また、複数行に渡るコメントは/* */を使って書くことができます。
オブジェクトの値を出力するときは、echoを使います。変数とデータ型
変数は、
$変数名 = 値;として宣言します。variable.php<?php $str = '文字列'; // 文字列 echo $str; $num = 123; // 数値 echo $num; $isBoolean = true; // 真偽値 echo $isBoolean; $arr = array('Python', 'R', 'JavaScript', 'PHP'); // 配列 echo $arr; $dict = array( 'Python' => 'AI', 'R' => 'statistics', 'JavaScript' => 'web', 'PHP' => 'WordPress' ); // 連想配列 echo $dict; ?>制御文
条件分岐
if文
if文は、
if (条件) {処理}と記述します。
さらに場合分けするときは、elseやelse ifを使います。if.php<?php num = 3; if ($num == 1) { echo '1'; } else if ($num == 2) { echo '2'; } else if ($num == 3) { echo '3'; } else { echo '1でも2でも3でもない'; } ?>switch文
if文で場合分けが多くなる場合は、switch文を使うと簡単に書けます。
switch文は、switch (変数) {case 値: 処理; ・・・}と記述します。switch.php<?php $num = 3; switch ($num) { case 1: echo '1'; break; case 2: echo '2'; break; case 3: echo '3'; break; default: echo '1でも2でも3でもない'; break; } ?>なお、
breakを書かないと、条件に合った場合でも後続のcaseの処理が走ってしまうので注意が必要です。反復処理
for文
一定回数だけ繰り返し処理を実行する(繰り返し回数があらかじめ分かっている)ときは、for文を用います。
for文は、for ($変数名 = 初期値; $変数 <= 最大値; $変数の値の更新)と記述します。for.php<?php for ($i = 1; $i <= 5; $i++) { echo $i; } ?>ここで、
$i++となっているのは、$i = $i + 1すなわち反復処理を繰り返すごとに変数$iを1ずつ大きくしていくことを示しています。これは、$i += 1とも書けますが、変数の値を1ずつ更新していく場合は、この書き方はあまりせず、$i++または$i--と書きます。変数の更新幅が1以外の場合は、$i += 2のように書きます。また、
foreach ($配列 as $変数名) {処理}とすると、配列の要素を一つずつ取り出すことができます。foreach.php<?php $arr = array('Python', 'R', 'JavaScript', 'PHP'); foreach ($arr as $lang) { echo $lang; } ?>while文
ある条件を満たす間だけ処理を繰り返す場合は、while文を用います。
while文は、while (条件) {処理}と記述します。while.php<?php $i = 1; while ($i < 5) { echo $i; $i++; // 変数の値の更新 } ?>関数
関数は、
function 関数名(引数){処理}とすることで作成できます。function.php<?php function copyNumber($num) { return $num; } echo copyNumber(123); ?>クラス
クラスは、
class クラス名 {プロパティ、メソッドなどの内容}とすることで作成できます。
newを使うことで、新しいインスタンスを生成できます。class.php<?php class Human { public $name; public $age; public function introduceMyself() { return 'I am ' + $this->name + ' and ' + $this->age + ' years old. '; } } $yukiya = new Human(); $yukiya->name = 'Yukiya'; $yukiya->age = 31; echo $yukiya->name; echo $yukiya->age; echo $yukiya->introduceMyself(); ?>まとめ
ここでは、PHPの基本的な文法について解説しました。
ウェブページのソースコードを見るときに役立ちます。
- 投稿日:2020-03-01T15:08:45+09:00
CSS設計完全ガイドで学んだPRE_CSSをElmで堅牢に実装する
皆さんは、CSSが得意ですか? 私は大の苦手で、ずっと避け続けています。しかし、何度も何度も向きあおうとしていました。もし共感していただける方には朗報です。
と言う懇切丁寧で、PRE_CSSと言うCSSモジュール設計(OOCSSやBEMなどの仲間です)の設計者本人が書かれた書籍になります。まだ読み初めではありますが、とても感銘を受けた書籍のためレビューの代わりに、Elmでとにかく実践してみると言う形で恩返しをさせていただきます。興味が湧かれた方は是非、購入を検討してみてください。
今回の記事は、肩慣らしということでエレメントモジュール(3.2参照)のボタンに焦点を当ててみました。コードは、こちら
まずは、愚直に実装してみる
今回対象となるボタンは「標準ボタン」「標準ボタンのDisabled状態」「前にアイコンがあるボタン」の三つになります。今回はViewだけに焦点を当てるため、Modelはどのボタンが押されたかの状態を表すもの。Msgもそれに合わせたものになります。
module Main exposing (main) import Browser import Html exposing (Html, a, article, div, p, text) import Html.Attributes exposing (class, href) import Html.Events exposing (onClick) -- MAIN main : Program () Model Msg main = Browser.element { init = init , update = update , view = view , subscriptions = subscriptions } -- MODEL type ClickState = Standard | Downloading | UnClick type alias Model = ClickState init : () -> ( Model, Cmd Msg ) init _ = ( UnClick, Cmd.none ) -- UPDATE type Msg = StandardClick | DownloadClick update : Msg -> Model -> ( Model, Cmd Msg ) update msg _ = case msg of StandardClick -> ( Standard, Cmd.none ) DownloadClick -> ( Downloading, Cmd.none ) -- VIEW view : Model -> Html Msg view model = article [ class "ly_cont" ] [ div [ class "bl_buttonSimulation" ] [ a [ class "el_btn", onClick StandardClick, href "#" ] [ text "標準ボタン" ] , a [ class "el_btn el_btn__disabled", href "#" ] [ text "標準ボタン" ] , a [ class "el_beforeIconBtn el_beforeIconBtn__download", onClick DownloadClick, href "#" ] [ text "ダウンロード" ] , p [ class "bl_buttonSimulation_clickState" ] [ text <| case model of Standard -> "標準ボタンがクリックされました" Downloading -> "ダウンロードボタンがクリックされました" UnClick -> "" ] ] ] subscriptions : Model -> Sub Msg subscriptions _ = Sub.noneエレメントモジュールのように、どのページ・レイアウトでも使われる可能性があるため、上記のコードのように剥き出しのHTMLタグのままコピー&ペーストを繰り返して開発・運用してしまうと変更に弱く・抽象度が低いためコードの見通しも悪くなってしまいます。
エレメントモジュールを疎結合にする
ElmはSPA時のページ単位ぐらいの粒度以外でのコンポーネント指向は現在推奨されていません(推奨されている時代もありましたが)。しかし、常にコードを具体的に書くという意味ではなく、むしろエレメントモジュールのような
Html msgについては積極的にモジュール分割や関数による抽象化を推し進めるべきでしょう。今回は、アイコンとボタンにフォーカスを当てて分割と抽象化を行いました。一つのアプリケーションにおけるアイコンの使用量は基本的に現実的な数で納まり、使用箇所や変更が簡単に行えると実に堅牢です。愚直に書くケースではclass文字列でコードが散りばめられる可能性がありましたが、カスタムタイプとモディファイア(3.3参照)への変換関数を用いて使用することで安全に使用することができます。
module Element.Icon exposing (Icon(..), toButtonModifier) type Icon = Download toButtonModifier : Icon -> String toButtonModifier icon = case icon of Download -> "download"ボタン用のモジュールを作り、画面ごとのオレオレボタンが生成されるのを防ぎましょう。Disabled状態のボタンはMsgを発行される必要がないためプロパティとしてdisabledフラグを受け取ることで、onClickを持つボタンとそうでないボタンに分岐してカプセル化を行いました。beforeIconButtonは先ほど定義したIconを受け取り中で変換することで、確実に存在するアイコンだけを使うことができるようになりました。とても堅牢です。
module Element.Button exposing (beforeIconButton, standardButton) import Element.Icon as Icon import Html exposing (Html, a, text) import Html.Attributes exposing (class, href) import Html.Events exposing (onClick) type alias ButtonProps msg = { text : String , onClick : msg , disabled : Bool } standardButton : ButtonProps msg -> Html msg standardButton props = if props.disabled then a [ class "el_btn el_btn__disabled", href "#" ] [ text props.text ] else a [ class "el_btn", onClick props.onClick, href "#" ] [ text props.text ] type alias IconButtonProps msg = { text : String , onClick : msg , icon : Icon.Icon } beforeIconButton : IconButtonProps msg -> Html msg beforeIconButton props = let iconModifier = "el_beforeIconBtn__" ++ Icon.toButtonModifier props.icon in a [ class <| "el_beforeIconBtn " ++ iconModifier, onClick props.onClick, href "#" ] [ text props.text ]最後は、定義したボタンの関数を呼び出して使ってみましょう。このようにコンポーネント指向でなくても、見通しが高く・再利用性と拡張性に優れたモジュールを作ることができました。ここでのポイントはMsgやボタンの状態であるdisabledなどを閉じ込めずに、プロパティとして渡すことができ、それらは使用側でハンドリングすることです。
view : Model -> Html Msg view model = article [ class "ly_cont" ] [ div [ class "bl_buttonSimulation" ] [ standardButton { onClick = StandardClick, disabled = False, text = "標準ボタン" } , standardButton { onClick = StandardClick, disabled = True, text = "標準ボタン" } , beforeIconButton { onClick = DownloadClick, icon = Icon.Download, text = "ダウンロード" } , p [ class "bl_buttonSimulation_clickState" ] [ text <| case model of Standard -> "標準ボタンがクリックされました" Downloading -> "ダウンロードボタンがクリックされました" UnClick -> "" ] ] ]まとめ
CSSが大の苦手な私ですが、Elmでさらに堅牢な高いコードにし、実践スピードの速さを上げ、最高の書籍を駆使することで克服の一歩を歩めています。ありがとうございます。CSSもElmもどちらもがんばっていきましょう。
- 投稿日:2020-03-01T15:03:12+09:00
HTML/CSS/JSデモ
HTML/CSS/JSが動くか試してみよう!!
今回は、Visual Studio Codeでプロジェクトを入れるためのフォルダ内にそれぞれのファイルを作り、動くか試して行きたいと思います
今回の記事
- HTMLでWebページの土台ファイルを作ろう!
- CSSで見た目の調整をしよう!
- リセットCSSを入れよう!
- JavaScriptで動きを付けよう!
- jQueryでJavaScriptを簡単に導入しよう!
HTMLでWebページの土台ファイルを作ろう!
HTMLとは「Hyper Text Markup Langage」の略で、簡単に言うとWebページの土台となるファイルを作成する言語です。
Webページに表示させたい文章などを「<」「>」で挟まれた「タグ」と呼ばれる文字列で囲んでいきます。
詳しい書き方については別記事にまとめてありますので、今回はHTMLがブラウザーで反映できるか試してみましょう。
※プロジェクトを入れるためのフォルダは作成済みとします。プロジェクトを入れるためのフォルダの作り方はこの記事参照
Visual Studio Codeの導入と使い方HTMLのファイルを作ってみましょう!
HTMLファイルに試しに書き込んでいきます。
「!」を入れるとVisual Studio Codeでは自動でHTMLの型を作成してくれます。
titleタグの中身を変更したり、bodyタグの中にh1タグ(見出し)やpタグを追加してみます。
HTMLが反映されているか見てみましょう。
index.htmlファイルの上で右クリック→エクスプローラーで表示する
クロームのマークが入ったindex.htmlが表示されます。
※うまくいかない場合はVisual Studio Codeでしっかり上書き保存しているか確認しましょう
これでHTMLファイルは反映されました。
CSSで見た目の調整をしよう!
次はCSSファイルを作り、反映させてみます。
CSSとは、「Cascading Style Sheets」の略で、見た目の装飾をするするための言語です。
つまり先ほど作成したHTMLの見た目を華やかに装飾できるのです。先ほどのHTMLで作成したWebページは白い背景、黒い文字ですが、文字の色を変えたり、大きさを変えたり、レイアウトの変更ができます。
試しにh1タグ(見出し)の色を変えるコードをCSSファイルに入力します。
sample.cssh1{ color: red; }しかしこれだけでは、CSSは反映されません。
HTMLファイルにCSSのファイルを読み込ませる必要があります。
index.html<link rel="stylesheet" href="">herfの中にCSSのファイルがある場所を書きます。
エクスプローラーから実行し、CSSが反映されていることを確認しましょう。
リセットCSSを入れよう
リセットCSSとは、ブラウザによって異なるデフォルトのCSSを打ち消してブラウザ間の表示を揃えるためのCSSファイルのことを言います。
リセットCSSを使用すれば、Google ChromeやSafari、Internet Explorerなど異なるブラウザを使っても同じようにWebサイトが表示されます。
リセットCSSは基本的にコピペでOK!
2019年版!おすすめのリセットCSSまとめCSSフォルダの中にreset.cssファイルを作成し、コピーしてきたものを張ります。
先ほどと同じようにHTMLファイルに反映されておきましょう。
リセットCSSを反映させ、エクスプローラーで表示するとこんな感じになります。
このようにデフォルトのCSSを打ち消すのでh1の文字の大きさなども自分で設定することが可能です。
今回はリセットCSSを入れた結果どのような表示になるか見てみたのですが、本来はプロジェクトを作成した際に最初にリセットCSSを入れておくことをオススメします。JavaScriptで動きを付けよう!
JavaScriptは簡単いうとWEBサイトに「動き」を加えるものです。
例えば、目立たせたいボタンやメニューにアニメーションや効果を追加したりできるので、ユーザーが使いやすいサイトを作ることが可能なのです。index.html<script type="text/javascript" src=""></script>srcの中にJSファイルがある場所を書きます。
わかりやすいようにHTML内にボタンを配置し、ボタンを押したら何か動きが出るようにしてみましょう。
sample.jsfunction btnClick() { alert("ボタンをクリックしましたね。"); }HTMLのボタンタグの中にもこのように追加しましょう。
index.html<button onclick="btnClick()">ボタン</button>jQueryでJavaScriptを簡単に導入しよう!
jQueryは、簡単に言うとJavaScriptを使いやすいように拡張してくれているものです。
「JavaScriptのライブラリ」なので、中身事態はJavaScriptです。
余裕がある人はjQueryを導入してみましょう。sample.js<script type="text/javascript"src="https://code.jquery.com/jquery-3.4.1.js"></script>jQueryの公式サイトからのコピペがおすすめです
https://code.jquery.com/先ほどのJavaScriptのコードをjQueryに書き換えてみましょう。
sample.js$(function() { $('button').click(function() { alert("jQuery:ボタンがクリック") }); });index.html<button>ボタン</button>お疲れ様でした
HTML/CSS/JSのざっくりとした説明と、読み込み方法でした。
今後は1つ1つじっくり勉強していきましょう
- 投稿日:2020-03-01T13:10:13+09:00
Vuetifyでv-text-fieldの中の文字をセンタリングする方法
なんの記事?
Vuetify の v-text-field の中の文字はデフォルトでは左寄せ。
それをセンタリングしたい時にどうするか。dispbarcode.comを開発した際に実施した内容を記事化しました。
方法
以下のCSSを作成しておき、それを v-text-field に適用する。
.centered-input input { text-align: center }コード抜粋
index.vue<!-- 抜粋です --> <v-row row justify-center align-center> <v-col xs12 class="d-flex justify-center"> <v-text-field label="code" v-model="barcodeValue" class="centered-input" :hide-details="true" :change="changeWidth()" placeholder="input code" /> </v-col> </v-row>表示例
「code」と「name」のところが v-text-field になっています。
「format」は v-select です。
わかっていない点
v-select の文言をセンタリングする方法がわかっていません。
v-text-field と同じ方法ではうまくいきませんでした。(左寄せのまま)参考
- 投稿日:2020-03-01T06:46:13+09:00
Font Awesomeのアイコンを導入してみる
目的
サイトを作成するのに無料アイコンが必要だったので導入しました。
備忘録流れ
1.登録:メール
下記のサイトにアクセスし、スタートをクリック
https://fontawesome.com/
下記に、自分のメールアドレスを入力し、「Send Kit Code」!
こんなメールが来るので「Click to Confirm Your Email Address + Set Things Up」!
2.登録:入力
上から「ファーストネーム」、「ラストネーム」…「最初に使ったのはいつか」
って感じで入力。そのままですね
3.リンク取得
入力が終わると下記のような画面に移動します。
ここで表示されているコードをコピーしてホームページにCOPYすればOK<script src="https://kit.fontawesome.com/{serial}.js" crossorigin="anonymous"></script>例もダウンロードできます。
<!doctype html> <html> <head> <!-- Place your kit's code here --> <script src="https://kit.fontawesome.com/{serial}.js" crossorigin="anonymous"></script> </head> <body> <i class="fas fa-thumbs-up fa-5x"></i> </body> </html>詰まったところ
フォントの最初の文字が異なる場合がある
フォントによって最初に入ってくる文字が異なる場合がります。
これがバージョンによって異なるらしく…注意が必要です。fa s
<i class="fas fa-map-marked-alt fa-3x"></i>fa b
<i class="fab fa-linkedin fa-1x"></i>こちらのサイトにあるような分類になっているようです。
https://fontawesome.com/how-to-use/on-the-web/referencing-icons/basic-useその他
ライセンス
ここにあります。
今日(2020年3月1日)現在はGPLライセンスのようです。
https://fontawesome.com/license/free設定変更
設定を変更し、過去のバージョンも利用できるようです。
また、SVGかWebフォントも選択可能です。kitのホーム画面で「your kit settings.」をクリックすると下記の画面に移動します。
- 投稿日:2020-03-01T02:51:29+09:00
CSSを使って長い文章を三点リーダーで省略する方法
ECサイトを作成していると、長すぎる商品名などが影響してしまい2行-3行と改行してレイアウトがガタガタになってしまうことがあります。そんなときに続きの箇所を「…」と表示して一列にまとめる方法を紹介します。
対処方法
CSSで実装していきます。例えば、例として下記のような商品。
HTML<div class="compact">2019年度〇〇賞受賞。福岡県産!〇〇米10kg</div>CSS.compact { width: 300px; /* 要素の横幅を指定 */ white-space: nowrap; /* 横幅のMAXに達しても改行しない */ overflow: hidden; /* ハミ出した部分を隠す */ text-overflow: ellipsis; /* 「…」と省略 */ -webkit-text-overflow: ellipsis; /* Safari */ -o-text-overflow: ellipsis; /* Opera */ }






















































