Quantcast
Viewing all 8524 articles
Browse latest View live

HTML と CSS だけでコードエディタっぽいものを作る (contenteditable, CSS Counter, ::before pseudo-element, Flexbox)

行番号を右側に表示できてテキストを折り返して表示できる JS 不要の入力ボックスを作る。
完成品はこちら。

See the Pen Text Editor by Mizuki HAGIMOTO (@mizkichan) on CodePen.

編集可能な要素を作る

<textarea>はスタイリングが難しすぎる。<input type="text">だと1行しか入力できず、複数並べて使おうとすると JS が必須になる。そこで contenteditable属性1を使う。

index.html
<divid="editor"contenteditable="true"></div>

これで内容を編集できる <div>要素が作れる。それぞれの行は <div>要素で囲まれて <div id="editor">の子ノードになる。たとえば foobarと入力すると

<divid="editor"contenteditable="true"><div>foo</div><div>bar</div></div>

のような形で DOM に格納される2

行番号を表示する

行番号の計算と表示には CSS Counter が使える。counter-incrementプロパティ3を使うと、このプロパティが適用される要素が現れるたびにカウンタを加算することができる。各行の <div>要素にこのプロパティを適用することで、行番号カウンタ linenoが加算されるようにする。

style.css
#editor>div{counter-increment:lineno;}

カウンタの値は counter()関数4を使って文字列に変換でき、 contentプロパティ5の値として用いることができる。各行の <div>要素の ::before疑似要素6にこれらのプロパティを適用することで、各行の直前に行番号を表示することができる。

style.css
#editor>div::before{content:counter(lineno);}

レイアウト等を調整する

表示すべきものはこれで十分だが、行番号とテキストとの間に隙間が全くないし、行番号が2桁になるとその行からテキストの表示開始位置がズレてしまうし、テキストが長くなると折り返しが行番号の真下から始まってしまう。

まず行番号をどうにかしよう。::before疑似要素は他のインライン要素と同様の仕方でスタイリングできる。インラインブロック要素に変更して、幅を指定して、右側にマージンをつけて、右寄せにする。

style.css
#editor>div::before{display:inline-block;width:2em;margin-right:1em;text-align:right;}

テキスト折り返しを考慮しないなら7これだけでよいが、考慮するならもう一工夫いる。要するに、1行ごとに行番号とテキストが同じ高さで並ぶようにすればよい。Flexbox8の出番である。この場合は単に displayプロパティの値に flexを指定すればよい。先ほどの例の #editor > div::before { display: inline-block; }は削除されたい。

style.css
#editor>div{display:flex;}#editor>div::before{/* 削除: display: inline-block; */}

おわりに

これだけでは勝手が悪い動作をするので何だかんだいっても JS でイベントを待機していじくり回す必要がある。がんばれ。


  1. https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable 

  2. 正確には、改行を入力しない場合は単一のテキストノードが <div id="editor">の直接の子ノードになるという例外がある。この記事における実装では、改行なしの一行しかテキストがない場合のスタイルを用意していないので、その場合は行番号が表示されない。そもそも CSS でテキストノードないしはテキストノードしか含まない要素を選択する方法がない(と思う)。 

  3. https://developer.mozilla.org/en-US/docs/Web/CSS/counter-increment 

  4. https://developer.mozilla.org/en-US/docs/Web/CSS/counter 

  5. https://developer.mozilla.org/en-US/docs/Web/CSS/content 

  6. https://developer.mozilla.org/en-US/docs/Web/CSS/::before 

  7. white-space: nowrap;を指定する。https://developer.mozilla.org/en-US/docs/Web/CSS/white-space 

  8. https://developer.mozilla.org/en-US/docs/Glossary/Flexbox 


BEM記法でSCSSを書く際のほんのちょっとしたテクニック

BEMでscssを書く際のちょっとしたテクニックです。本当にちょっとしたものです。
既に、ご紹介されている方はたくさんいますが、
最近知り、地味に感動したので、自分用のメモに残しておきます。

BEMを書いているとよくあるこんなこと

blockに対するmodifierの下位elementにスタイルを指定したいときのこんな書き方。

style.scss
// block.block{width:200px;height:200px;// element&__element{width:100px;height:100px;}// block modifier&--red{.block__element{width:150px;height:150px;background:red;}}}

気に入らない点

  • &で省略して書いているのに、ここに来て記述が長くなってしまう。
  • 上記に付随して、なんか気持ち悪い。なんかダサい。

理想の書き方

あくまで理想の書き方。当たり前だが、scssの仕様上、通らない。

bad.scss
// block.block{// 省略  // block modifier&--red{&__element{width:150px;height:150px;background:red;}}}

じゃあどうするか

block直下で&を変数にしておく。クラス名の場合はインターポーレションを使用する。

good.scss
// block.block{// 省略$and:&;// block modifier&--red{#{$and}__element{width:150px;height:150px;background:red;}}}

$andを使用しているが、何でもいいみたいです!
こうすることで、理想通りのスタイルを当てることができる。知らなかった。。。。

さいごに

BEMって難しい・・・

阿部寛と学ぶHTMLチュートリアル

はじめに

HTMLの分かりやすい解説サイトなんていくらでもあります。だけどあえて、僕が思う一番分かりやすいチュートリアルを書こうと思います。

よくある記事とは少し趣の違う記事になっていると思います。

HTMLを書く流れ

HTMLを書くときは、以下の2つを考えます。

  1. デザイン
  2. SEO(検索エンジン最適化)

一つずつ解説していくので今は分からなくて大丈夫です。

書き始める前の準備

HTMLを書き始める前に、VSCodeというコードエディタを入れましょう。コードエディタというのは、高性能なメモ帳のことです。

  1. ダウンロード&インストール
    公式サイトからVSCodeをダウンロードしてください。ダウンロードしたファイルを実行して、VSCodeをインストールしてください。

  2. 作業ディレクトリの作成
    デスクトップなど任意の場所に、html-sampleというディレクトリ(フォルダ)を作成してください。

  3. VSCodeを開く
    インストールしたVSCodeを起動し、『ファイル』タブから『開く』を選択し、で作成したhtml-sampleディレクトリを開いてください。
    Image may be NSFW.
    Clik here to view.
    vs_open

  4. 拡張機能をインストール
    左側にある Image may be NSFW.
    Clik here to view.
    vs_extends
    のマークをクリックして、『Live Server』を検索し、Live Serverをインストールしてください。

  5. HTMLファイルの作成
    左側にカーソルを持っていくと表示される Image may be NSFW.
    Clik here to view.
    vs_file
    マークをクリックして、index.htmlという名前のファイルを作成してください。

  6. 正しくインストールされたかの確認

index.htmlHello worldと書き込んで、右下にある Image may be NSFW.
Clik here to view.
Go Live
をクリックしてください。ブラウザが開いてHello worldと表示されれば大丈夫です。

Image may be NSFW.
Clik here to view.
Go Live
が表示されない場合は、

  • 拡張機能『Live Server』がインストールされていること
  • ファイル名の拡張子が.htmlであること

を確認した上で、VSCodeを再起動してみてください。

1. デザイン

見本として、阿部寛さんのホームページを真似して作ってみましょう。なぜ阿部寛さんのホームページかというと、有名だということと、デザインがシンプルだからです。

Image may be NSFW.
Clik here to view.
阿部寛のホームページ

HTMLで扱えるもの

HTMLはサイトの骨組みを作るものです。デザインはCSSを使います。

重要なのは、HTMLが扱えるのは『四角形の枠』だけということです。

四角形の枠だけでどうやってデザインするのか考えていきましょう。

構造を考える

四角形の枠は、以下の2つを指定することができます。

  • 大きさ
  • 周りの余白

実際に題材の構成を考えると、以下のようになります。

Image may be NSFW.
Clik here to view.
abe_gazou.jpg

枠だけにするとこんな感じです。

Image may be NSFW.
Clik here to view.
abe_waku.jpg

枠はそれぞれ大きさがあって、枠と枠の間は余白を持っていることがわかります。

Q&A

  • 文字が中央寄せになってるのはなぜ?
    • 文字はCSSで中央寄せ/右寄せ/左寄せすることができます。
  • 四角形の枠しか使えないって言ってるのに、左のメニューに丸いやつあるよ?
    • あれも四角形ですが、角を丸くしているだけです。

HTMLの構成について

HTMLは人の形をしています。

Image may be NSFW.
Clik here to view.
HTMLの構成

head(頭)には、サイトについての情報(タイトルやキーワードなど)が書かれています。

body(体)には、サイトのデザインの骨組みが書かれています。僕たちがWebサイトにアクセスしたときに目にするのはこちらの部分です。

HTMLの記法について

例えば、htmlという枠をこのように表現します。

<html></html>

このhtmlという枠の中にheadという枠とbodyという枠を入れると、こうなります。

<html><head></head><body></body></html>

イメージとしてはこんな感じです。

Image may be NSFW.
Clik here to view.
htmlとbodyの関係

[ ポイント ]

改行やスペースはあってもなくてもエラーにはなりません。

<html><body></body></html>

のように書いても問題ありませんが、見辛くなるので適宜改行しましょう。

また、メモ代わりにコメントを残すこともできます。

<!-- これはコメントです。 -->

実際に書く

"書き始める前の準備" で作成したindex.htmlに書き込んでいきましょう。

まず最初に、「これはHTMLファイルだよ」と宣言する文が必要です。

<!DOCTYPE html>

次に、HTML本体を書き始めます。これは先ほど説明した通りです。

<!DOCTYPE html><html><head></head><body></body></html>

デザインをするので、<head></head>には放置して、<body></body>の中に書いていきます。

bodyに書き込めるタグにはたくさん種類がありますが、デザインするためには<div></div>だけ使えば作ることができます。上の「構造を考える」で、すでに阿部寛のホームページの構造は把握しました。なので、それをただ<div></div>に置き換えるだけです。

Image may be NSFW.
Clik here to view.
アセット 1.jpg

<!DOCTYPE html><html><head></head><body><divclass="nav-wrapper"><divclass="nav"><divclass="nav-content"><divclass="nav-icon"></div>トップ</div><divclass="nav-content"><divclass="nav-icon"></div>映画出演</div><divclass="nav-content"><divclass="nav-icon"></div>ドラマ出演</div><divclass="nav-content"><divclass="nav-icon"></div>舞台出演</div><divclass="nav-content"><divclass="nav-icon"></div>写真集</div><divclass="nav-content"><divclass="nav-icon"></div>出版本</div><divclass="nav-content"><divclass="nav-icon"></div>管理者</div></div></div><divclass="contents-wrapper"><divclass="heading">阿部 寛のホームページ</div><divclass="contents-wrapper-inner"><divclass="content-left"><divclass="face-photo"></div><divclass="profile">阿部 寛(あべ ひろし)<br/>生年月日 1964年6月22日<br/>血液型 A型<br/>プロフィール
          </div><divclass="profile-english">
            If you have any enquiries regarding my TV drama or film, or would like to make an enquiry concerning future projects, please do not hesitate to contact me through the following email address.
          </div><divclass="mail">mail:shigeta@navy.plala.or.jp</div><divclass="profile-detail">所属:<br/>茂田オフィス<br/>
            107-0052<br/>東京都港区赤坂9-5-29<br/>赤坂ロイヤルマンション303<br/>
            TEL : +81-3-5410-8585<br/>
            FAX : +81-3-5410-0588<br/></div></div><divclass="content-right"><divclass="news-heading">★★★ 最新情報 ★★★</div><divclass="news-list"><divclass="news-list-content"><divclass="news-list-title">・舞台</div><divclass="news-list-description">彩の国シェイクスピア・シリーズ第35弾 『ヘンリー八世』 <br/>
                  2020年2月14日(金)~3月1日(日)
                </div></div><divclass="news-list-content"><divclass="news-list-title">・TV</div><divclass="news-list-description">「まだ結婚できない男」<br/>フジテレビ系連続ドラマ主演<br/>毎週火曜日 21時~ 放送中                   
                </div></div><divclass="news-list-content"><divclass="news-list-title">・映画</div><divclass="news-list-description">「HOKUSAI」 <br/>
                    2020年・初夏 全国公開New!<br/><br/>マレーシア映画「The Garden Of Evening Mists」<br/>
                    2020年公開予定
                </div></div></div></div></div></div></body></html>

ほとんど<div></div>しか使っていませんが、説明していないこともあるので補足します。

  • <div></div>の中に文字を入れることができます。文字は<br/>タグで改行できます。
  • このあと、それぞれの<div></div>にCSSを使って「大きさ」や「余白の大きさ」を指定していきます。そのときにそれぞれの<div></div>を判別する必要があるので、必ず<div class="名前"></div>のように名前を指定します。

この状態で Image may be NSFW.
Clik here to view.
Go Live
を押してHTMLを表示しても、何も表示されません。それは、<div></div>の大きさも余白も色も設定されていないからです。

CSSを使う

この記事はHTMLの解説なのでCSSを詳しくは説明しませんが、上記の<div></div>だけのHTMLでデザインができることを示すために、簡単なCSSを追加しましょう。

  1. index.html<head></head>を以下のように編集します。
<head><!-- CSSファイルを読み込む --><linkrel="stylesheet"href="./style.css"/></head>
  1. style.cssを作成して、以下を書き込みます。
*{margin:0;padding:0;border:solid1px;box-sizing:border-box;}body{display:flex;}.nav-wrapper{height:100vh;width:18%;}.nav{margin-top:43px;margin-left:18px;}.nav-content{display:flex;margin-bottom:31px;align-items:center;}.nav-icon{height:8px;width:8px;margin-right:9px;border-radius:50%;}.contents-wrapper{height:100%;width:82%;}.heading{margin-top:35px;margin-bottom:20px;text-align:center;font-size:32px;}.contents-wrapper-inner{display:flex;width:880px;margin:0auto;}.content-left{width:350px;}.face-photo{width:100%;height:414px;margin-bottom:23px;}.profile{margin-bottom:40px;margin-left:20px;}.profile-english{width:300px;margin-bottom:20px;}.mail{margin-bottom:20px;}.content-right{width:530px;}.news-heading{margin-bottom:30px;text-align:center;font-size:16px;font-weight:normal;}.news-list{margin-right:10px;margin-left:10px;}.news-list-title{margin-top:5px;margin-bottom:4px;}.news-list-description{margin-bottom:30px;margin-left:80px;}
  1. Image may be NSFW.
    Clik here to view.
    Go Live
    を押すと、以下のように表示されます。

Image may be NSFW.
Clik here to view.
フレームの画像

クラス名を指定して、高さ、余白、枠の色を指定しているだけですが、阿部寛のホームページのデザインがほとんどできていることが分かります。

CSSの内容についての補足

  • box-sizing: border-box; : 要素の大きさをボーダーに合わせます。全ての要素に適用しておくのがオススメです。

  • display: flex; : 通常縦並びにしかならない要素を、横向きに並べることができます。とても便利です。

  • margin: 0 auto; : 要素を中央寄せにします。よく使います。

2. SEO

ここまで<div></div>しか使用していませんが、デザインだけならここまでで完成しています。ユーザー目線で見れば、デザインが全てなので、これで十分でしょう。しかし、Googleなどのクローラ(自動で巡回してくるロボット)から見るとどうでしょう。全てが<div></div>で書かれていると、どこが題名なのか、どこがナビゲーションバーなのか分かりません。すると、Googleは内容が薄いと判断して、検索結果に表示されにくくします。阿部寛のホームページも含め、Webサイトは多くの人に見てほしいので、検索結果に表示されやすくするべきです。この『Googleなどの検索結果に表示されやすくする』ための技術をSEOと言います。

セマンティクス

divというのは、ただの枠で、意味を持ちません。なので、題名やナビゲーションバーなどの特別な枠には、意味を持ったタグを使うべきです。この『意味を持つ枠には、意味を持つタグを使うべき』という考え方をセマンティクスと呼びます。

意味のあるタグはたくさんありますが、一例を挙げます。

  • nav : ナビゲーションバー。
  • ulとli : 箇条書きのリスト。
  • dlとdtとdd : 定義リスト。
  • h1 : 表題。1ページにつき1つだけあるべき。

  • p : 段落。

  • span : 特に意味はない。文字を装飾するためのタグ。

それを踏まえた上で、index.htmlを書き直してみます。

<!DOCTYPE html><html><head><linkrel="stylesheet"href="./style.css"/></head><body><navclass="nav-wrapper"><ulclass="nav"><liclass="nav-content"><spanclass="nav-icon"></span>トップ</li><liclass="nav-content"><spanclass="nav-icon"></span>映画出演</li><liclass="nav-content"><spanclass="nav-icon"></span>ドラマ出演</li><liclass="nav-content"><spanclass="nav-icon"></span>舞台出演</li><liclass="nav-content"><spanclass="nav-icon"></span>写真集</li><liclass="nav-content"><spanclass="nav-icon"></span>出版本</li><liclass="nav-content"><spanclass="nav-icon"></span>管理者</li></ul></nav><divclass="contents-wrapper"><h1class="heading">阿部 寛のホームページ</h1><divclass="contents-wrapper-inner"><divclass="content-left"><divclass="face-photo"></div><pclass="profile">阿部 寛(あべ ひろし)<br/>生年月日 1964年6月22日<br/>血液型 A型<br/>プロフィール
          </p><pclass="profile-english">
            If you have any enquiries regarding my TV drama or film, or would like to make an enquiry concerning future projects, please do not hesitate to contact me through the following email address.
          </p><pclass="mail">mail:shigeta@navy.plala.or.jp</p><pclass="profile-detail">所属:<br/>茂田オフィス<br/>
            107-0052<br/>東京都港区赤坂9-5-29<br/>赤坂ロイヤルマンション303<br/>
            TEL : +81-3-5410-8585<br/>
            FAX : +81-3-5410-0588<br/></p></div><divclass="content-right"><h2class="news-heading">★★★ 最新情報 ★★★</h2><divclass="news-list"><dlclass="news-list-content"><dtclass="news-list-title">・舞台</dt><ddclass="news-list-description">彩の国シェイクスピア・シリーズ第35弾 『ヘンリー八世』 <br/>
                  2020年2月14日(金)~3月1日(日)
                </dd></dl><dlclass="news-list-content"><dtclass="news-list-title">・TV</dt><ddclass="news-list-description">「まだ結婚できない男」<br/>フジテレビ系連続ドラマ主演<br/>毎週火曜日 21時~ 放送中                   
                </dd></div><dlclass="news-list-content"><dtclass="news-list-title">・映画</dt><ddclass="news-list-description">「HOKUSAI」 <br/>
                    2020年・初夏 全国公開New!<br/><br/>マレーシア映画「The Garden Of Evening Mists」<br/>
                    2020年公開予定
                </dd></dl></div></div></div></div></body></html>

ユーザーから見たデザインに変化はありませんが、ソースコードを見ただけで、題名(h1)の場所や、ナビゲーションバー(nav)の場所が分かります。

head内の要素

ここまでbodyにしか書き込んできませんでしたが、SEOの観点でも、ユーザビリティの観点でも、headはとても重要になります。実際に、主要なタグをindex.html<head></head>内に書き込んでみましょう。

<head><!-- ページのタイトル --><title>阿部寛のホームページ</title><!-- ページの説明 --><metaname="description"content="俳優の阿部寛のプロフィールや最新情報を発信するホームページです。"/><!-- 文字エンコードの指定 --><metacharset="utf-8"/><!-- URLを正規化(統一化)する --><linkrel="canonical"href="http://abehiroshi.la.coocan.jp/"/><!-- スマホに対応するためのViewPortの設定 --><metaname="viewport"content="width=device-width,initial-scale=1"/><!-- OGP(SNSでの表示)の各種設定 --><metaname="twitter:card"content="summary_large_image"/><metaname="twitter:site"content="@abehiroshi"/><metaproperty="og:url"content="http://abehiroshi.la.coocan.jp/"/><metaproperty="og:title"content="阿部寛のホームページ"/><metaproperty="og:description"content="俳優の阿部寛のプロフィールや最新情報を発信するホームページです。"/><metaproperty="og:image"content="http://abehiroshi.la.coocan.jp/abe-top-20190328-2.jpg"/><!-- ホーム画面に追加したときのタイトル --><metaname="apple-mobile-web-app-title"content="阿部寛のホームページ"><!-- ホーム画面に追加したときの画像 --><linkrel="apple-touch-icon"sizes="180x180"href="./assets/apple-touch-icon.png"><!-- サイトアイコンの指定 --><linkrel="icon"type="image/png"href="./assets/icon-192x192.png"><!-- 電話番号などをリンクに変換されるのを防ぐ --><metaname="format-detection"content="email=no,telephone=no,address=no"/><linkrel="stylesheet"href="./style.css"/><!-- CSSファイルを読み込む --></head>

覚えなくていいです。コピペして、必要なところを書き換えれるようになりましょう。

さいごに

僕が最初に買ったHTMLの入門書は、いきなりh1ulliのようにタグを使用していたので、僕は「なんでdivじゃなくてh1なんかを使うんだろう」とモヤモヤした経験があります。そんな疑問を読者に持たせないように、「シンプルなdivだけで簡単にデザインは作れるんだ」と伝えたくてこの記事を書きました。

ご意見やご質問はコメント欄かTwitterまでお願いします。

CSSで複数行3点リーダーを実現する「line-clamp」がついにIE以外に対応したよ!

最新のFirefoxが対応した!!

これにより、IEさえ無視すれば完全にCSSのみで複数行の3点リーダーが実現可能となりました。
よく使う割に難しい処理だったので、地味に嬉しいです。
最新の対応状況はこちら

line-clampとは?

行数を指定して、テキストが領域をはみ出した時に3点リーダ「…」を表示することができます。

Image may be NSFW.
Clik here to view.
スクリーンショット 2019-10-30 16.41.29.png

//cssp{width:170px;display:-webkit-box;-webkit-line-clamp:4;//3点リーダを表示する行数を指定-webkit-box-orient:vertical;overflow:hidden;}

たったこれだけで実現できちゃいます。
詳しくはこちらの記事でわかりやすく書かれています。

現状のベストプラクティス

・IE以外で簡単に実現できるようになったので、基本line-clampを使いましょう。
・IE対応が必要な場合は、引き続きIEのみ従来のJSや擬似要素で無理やり実現する方法などで対応しましょう。

自分流: Emotionでのスタイルの書き方

この記事について

内容を一言でまとめると

  • CSS in JSのライブラリであるEmotionを使ってどのようにスタイルを書いているか

何故書いたか

  • Emotionの機能紹介のような記事は多いけど、実際のプロダクトに当てはめて書き方を紹介している例が少ないなあと思ったから
  • 自分なりに考えて一旦書き方が定まってきたから
  • 今のやり方を公開すればこれを叩き台にしてもっと良いやり方が生まれていく可能性もあるから

こんな人が読んでくれたら嬉しい

  • CSS in JSのライブラリは一杯あるけど、それぞれ具体的なメリットデメリットがわからん!と困っている人
  • npm trendsでstyled-componentを抜いたし、気にはなっているけど若干尻込みしている人
  • 実際にEmotionを使っているけど書き方が定まらない人

目次

  • css Propを使用する
  • reset cssなど全体に渡るものはGlobalに指定しておく
  • 色、フォントサイズ、グリッドのサイズは定数で指定する
  • アニメーションは極力keyframesを利用してCSSアニメーションで実装
  • ケーススタディ
  • まとめ

css Propを使用する

  • css Propの中でもString Stylesの書き方で書く
    • CSSのプロパティとして調べるにしても、過去のアセットをコピペするにしてもObject Stylesだとしんどい
  • styled componentsは使用しない(emotion/styled自体インストールしていない)
    • スタイリングのためだけのコンポーネントが増えすぎる
    • ファイルとして分けたコンポーネントと、コンポーネントの中でローカルに(?)増えていくコンポーネントが対応しなくて管理がつらい

基本的には全ての要素にcss={foo}と指定する

  • 以下のように、全てのエレメントにcssを指定
OK例
constcontainer=css`
  background-color: #eee;
`consttext=css`
  color: hotpink;
`render(<divcss={container}><pcss={text}>this is hotpink</p></div>)
  • 逆にNGなのは以下
    • 入れ子でタグを直接指定してスタイリングするとスコープが閉じなくなる
    • 例のコードだとchildrenやComponent内で呼び出したComponentに<p>が存在しているとそれもhotpinkになってしまう
      (ならないようにも書けるけど頭の中で完璧にシミュレートするのは難しいので初めから採用しない)
NG例
constcontainer=css`
  background-color: #eee;
  p {
    color: hotpink;
  }
`consttext=css`
  color: hotpink;
`render(<divcss={container}><p>this is hotpink</p></div>)
  • 例外→ol, ul > lidl > dt, ddtable > th, td
    • lidttd1つ1つ全部にcss Propを渡すと余計にコーディングが大変になるため
例外
constfoo=css`
  background-color: #eee;
  li {
    padding: 5px 0;
  }
`render(<ulcss={foo}><li>なんとか</li><li>かんとか</li><li>うんたら</li>{/* li1つ1つに全部css={bar}とか書くのは変更が発生したときに漏れを起こしやすい */}</ul>)

reset cssなど全体に渡るものはGlobalに指定しておく

  • Emotionに組み込まれているGlobalコンポーネントを使うと文字通りグローバルにスタイルを適用できる
  • reset cssなどは初めにこの中で設定しておきトップレベルで呼ぶようにしておくと楽
  • 注意点
    • @emotion/coreからGlobalをimportする必要があることと
    • cssを使っていた箇所をstylesに変える必要があること
  • 具体例
Global
import{Global,css}from'@emotion/core'{/* Globalを使用する際はimportの必要あり、通常はcssだけをimportすれば良いので記述を省いていた */}constglobal=css`
  * {
    margin: 0;
    padding: 0;
  }
`render(<Globalstyles={global}/>)

色、フォントサイズ、グリッドのサイズは定数で指定する

  • %指定がしたいとき、beforeafterで図形を描画するとき、positionで要素を配置するときは直接値を書いても良い
    • CSSで矢印を描く……などのときは流石に定数では対応しきれないこともある
  • 以下は自分のポートフォリオサイトで使っている定数の例
    • このように指定しておけばエディタが補完してくれる
    • 複数人で開発する際の「微妙に違うブルーが50種類くらい存在する」みたいな事象を回避できる
Color.js
exportdefault{White:'#fff',Gray20:'f2f2f2',Gray50:'#dfe0e0',Gray80:'#c8c9ca',Gray100:'#abacae',Gray300:'#8b8e90',Gray400:'#6c6f72',Gray600:'#4d5154',Gray700:'#2f3438',Gray800:'#161c20',Black:'#040b0e',Blue:'#0074af',}

アニメーションは極力keyframesを利用してCSSアニメーションで実装

  • アニメーション用のライブラリを使った方が短期的には楽かもしれないけど、ライブラリの選定自体が難しかったり後から使いづらくなると修正が難しくなったりするので自分で書くのを前提とする
  • JSを使ってアニメーションをするとレンダリングブロックの原因にもなる
  • 以下に示す例ではreact-intersection-observerを使用
    • これ自体の説明は省略、要はタイミング制御用にJSでクラスを付け外しするにとどめるという話
アニメーションの例
consttext=css`
  color: hotpink;
  opacity: 0;
  transform: translateY(-10px);
`constfadeIn=keyframes`
  100% {
    opacity: 1
    transform: translateY(0);
  }
`constanimation=css`
  animation: ${fadeInn} 1s ease-out both;
`constComponent=()=>{const[ref,inView]=useInView({rootMargin:'10px',threshold:0,})return(<pref={ref}css={[text,inView&&animation]}>
      animation
    </p>)}

ケーススタディ

以下は実際に迷ったケースの紹介と採用した書き方の紹介

コンポーネントが入れ子になる際のスタイリング

  • 前提
    • 便宜上呼び出す側を「親コンポーネント」、呼び出される側を「子コンポーネント」と呼ぶ
    • 複数の子コンポーネントが存在して、それぞれ処理は違えどスタイルは同じとする
  • 親コンポーネント内では以下のようにcss Propをexportする
Parent
exportconststyles=css`
  background-color: #eee;
  padding: 10px;
  width: 100%;
`
  • 子コンポーネントでは以下のようにcss Propをimportして、jsx内ではimportしたcss Propを割り当てるのみ
    • なお、親Componentからexportされたcss Prop以外をimportしてはしっちゃかめっちゃかになるのでNG
Child
import{styles}from'./Parent'render(<divcss={styles}>{/* 子コンポーネントの中身を書く */}</div>)

基本スタイルは一緒でバージョン違いのComponentがある場合

  • 前提
    • primaryなボタンとsecondaryなボタンがある
    • 文字サイズや角丸は一緒だけど文字色と背景色とボタンの幅が違う
  • scssでいう@extendが出来るのでそれを使う
    • 先に定義したスタイルを${}で囲って呼び出す、そのあとに上書きしたいプロパティを記述する
constbutton=css`
  border-radius: 4px;
  padding: 20px;
  font-size: 16px;
  font-weight: bold;
`constprimaryButton=css`
  ${button} {/* 基本のボタンのスタイルを引き継いでいる */}
  color: white;
  background-color: hotpink;
  width: 100%;
`constsecondaryButton=css`
  ${button} {/* 基本のボタンのスタイルを引き継いでいる */}
  color: black;
  background-color: gray;
  width: 200px;
`constPrimaryButton=()=>{<buttoncss={primaryButton}>primaryボタン</button>
{/* 文字色が白、背景色がピンク、幅が100%のボタン */}}constsecondaryButton=()=>{<buttoncss={secondaryButton}>secondaryボタン</button>
{/* 文字色が黒、背景色がグレー、幅が200pxのボタン */}}

Componentにはマージンを付けたくないが、実際の配置ではマージンが必要な場合

  • Attaching Propsを使って処理する
    • 通常、UIコンポーネントそのものにマージンをつけるのは良くない
    • しかし毎回コンポーネントをdivで囲ってレイアウト用のスタイルを書くのもマークアップが汚くなるのでこういうやり方
コンポーネント自体
{/* SomeComponentそのもののスタイル */}constchild=css`
  background-color: gray;
  border-radius: 4px;
  padding: 20px;
`constChildComponent=props=>(<divcss={child}{...props}>{/* ここにComponentの中身が書いてある */}</div>)
コンポーネントを呼び出す側
{/* SomeComponentをレイアウトするためのスタイル */}constlayout=css`
  margin-top: 20px;
`render(<SomeComponentcss={layout}/>{/* SomeComponentで指定しているcss={child}に加えてcss={layout}の内容も当たっている
      = margin-top:20pxが適用されている */})

まとめ

初めにも書いた通り、まだまだ良い書き方はたくさんあると思います。
しかし、自分自身が最初はかなり苦労しながら書き方の方向性を模索していたので、誰かにとっての手助けになれば嬉しいです。

便利そうなCSSメモ

概要

便利そうなCSSメモです。

scroll-behavior

See the Pen Scroll-Behavior by saka (@sakas) on CodePen.

scroll-behavior

scroll-behavior - Can I use ...

Chrome、Firefox対応済み、Edgeは76からサポート、Safariは未対応

はみ出した文字の省略

See the Pen Truncation Test by saka (@sakas) on CodePen.

  • text-overflow(単一行)
  • line-clump(複数行)

text-overflow - Can I use ...

line-clump - Can I use ...

line-clumpは最新バージョンで各ブラウザ対応してるみたいです。(IE以外)

毎日ブログ三日目

只今の時刻21:09分

今日も書いていきますか

今日やったこと

①Progate HTML/CSS上級道場

2回目ということもあり、前回よりかはできるようになりました。
しかしながら、ヘッダーとメインが難しい。
スマホとかタブレットのことを考えると、避けては通れない道なんですよね。

・学んだこと
メニューアイコンを消す方法
HTMLにfa fa-bars これをspanタグの中にクラスづける。このfaとかはどっかのサイトに載っているみたい。

このままだとPC画面にも表示されるので、display:none;で消します。

その代わりにスマホ用の所で、display:block;で表示させます。

あ、でも、元々あったヘッダーをスマホでは消す必要がある。

clearの使い方はよく分かりませんでした。
これを使わないと、四つの画像が上手く表示されないことや、文字が画像の方に入り混んでいたりしたので使うみたい。
clearで空タグを挟み込む感じかな。
それでバランスを取る感じ。

②ドットインストール
Boostrap4 15~22 この単元終了

まず、これの復習の前に

あるブログを読んでいたら、Boostrapは必要なくて、学ぶだけ時間の無駄みたいなこと言っている人がいて、「ええええええ!!!!まじかよっ!」って思いました。それみたせいで、やる気がガクーンって下がりました。ブログがあることはいいことなんですけど、時にはモチベーションを下げるもんだなと学びましたね。

・学んだこと
テーブルタグを使うことにより、行と列みたいに分けられるんですね。

テキスト アライン センターはテキスト センターでいける。

activeにするのは対応するタグにつけること。

角○の消し方、rounded-0

消すボタンの付け方は、alertを使って消すみたい。

今回やったところは、なんかやり方を納得する感じかな。知らないことばっかであんまりなあって感じっだった。
正直今回はやる気が下がっていたこともある、イマイチ身が入らなかった。でも、何となく概要は理解したので調べながらやればいけそう。

本当に無駄なのかな。。。

話は変わり、今日は二郎系ラーメンを昼に食べに行ったので夜ご飯は食てべません。二郎系は夜には食べず昼に食べることで、食費を浮かせられるのでかなりおすすめです。

また、話は変わり、富士山転落のニュースはとてもいたたまれない気持ちになりました。お悔やみ申し上げます。

明日のTo Do List
・Progte jQuery 初級編
・Progte FlexBox 終わらす
・模写 一番簡単そうなの

やることがよくわかんなくなってきたけど、模写はした方が良さそうなのでしたいと思います。そもそも画像ってどこから持ってくるのか書いて無くてわかりません。

他の人のブログをみていると、役に立つことは確かにあるけど、自分でどの情報を取捨選択する必要がありそうですね。今の世の中情報があふれすぎていて、三ヶ月で上達しますみたいに謳っていることが多いけど情報に振り回されず、こつこつやることが大事なんじゃないかなと思う今日この頃。

書き終えた時刻、22:13分

約1時間かかりました。どうも、cとxが打ちずらくてしょうがないですね。あと「。」打つのも大変。

明日は一限なので、朝しっかり起きないと、、
明日も一日頑張りましょう!!!!

chromeで不要なcssとJavaScriptを見つけ出し削除する

不要なcssとjavascriptを洗い出す方法

cssをコーディングしていて余分な部分があるか分からなかったり探すのが手間がかかっていたので、
便利なツールはないか探していたら不要な部分を洗い出す方法を乗せたサイトです。

https://www.ecomottblog.com/?p=609

consoleの横にあるメニューからCoverageを選択し開けば使用開始できるものです

Image may be NSFW.
Clik here to view.
無題.png


css の配置で回り込みを防ぐ方法(clearfix)

cssの配置の回り込みを防ぐぐ方法(clearfix)

方法を解説してあるサイトです

http://creator.aainc.co.jp/archives/4721

cssによって回り込みを防ぐ方法です
clear属性とdisplay属性でブロック表示、content属性で挿入する要素を使用します

この方法以外にも使用目的として正確ではないですがoverflow属性でもできるそうです

cssを使わない方法はdivを挿入するやり方もあります

チュートリアル - Vue.jsでPlayCanvas製3Dモデルビュワーサイトを作る 1 / 4

※このチュートリアルはPlayCanvas運営事務局で使用しているハンズオンの資料です。

PlayCanvasでWebサイトを作る

このチュートリアルでは、自作した3DモデルなどをWebから簡単に閲覧できるようにモデルビュワーを実装していきます。
今回はVue.jsというJavaScriptsのフレームワークを使います。
Vue.jsについて深堀しないため、精通していない方でも簡単に作ることができます。

できるページは以下のようなページができます。
https://playcanv.as/p/xIj6iJ4S/

3Dモデルビュワーということで、今回は3Dモデルを回転させたりズームしたりできます。
また、3Dモデルはメニューからクリックし変更することも可能で、各モデルごとに詳細を載せることも可能です。

今回は上記のものを作成するべく、チュートリアルを始めましょう。


Projectを新しく作ります

今までのハンズオンでは事前に用意したプロジェクトをForkして始めていましたが、今回は新規Project作成します。
Image may be NSFW.
Clik here to view.
0C15B660A01C8AECEF2D146FC15E269E.png

新規プロジェクトから以下の「Model Viewer Starter Kit」を選択します。
任意のプロジェクト名と詳細を記載してCREATEをクリックします。
Image may be NSFW.
Clik here to view.
BFEC57B47136E51E7DB587E96FF98A91.png

EDITORをクリックして、プロジェクトを編集します。
Image may be NSFW.
Clik here to view.
CBBC30C89A2696019EAF5E513A21245D.png


エディット画面のGUIの説明

まずSCENESの画面からeditするsceneを選びます。
Mainを選択。
Image may be NSFW.
Clik here to view.
65B658F0CB314A004B11C5AE311521A3.png

Untitledをクリックすると以下の画面になります。
Image may be NSFW.
Clik here to view.
A360B125905F2370477BA0E291702C70.png

上の画像を基に、GUIを簡単に説明していきます。
Image may be NSFW.
Clik here to view.
8AA097A5D93899E0CBE758F45CAFABAB.png

  1. メニュー(MENU)
    Photoshopなどでいうツールバーとメニューバーが合わさっている感じ。
    プロジェクトの設定やシーンの設定などもここで操作できます。

  2. ヒエラルキー(HIERARCHY)
    シーン内に存在するオブジェクトの一覧が表示されます。
    このシーンではどんなentityが使われているのかなどここで管理。
    まとめてグループ化や名前の変更などもできる。

  3. シーン(SCENE)
    3D空間が表示され、自由な位置・角度から眺めることができます。
    3DコンテンツやカメラのPositionやRotateなどを弄ることができます。

  4. アセット(ASSETS)
    プロジェクトhtmlやcss、imgといったファイルや3Dモデルのデータなどもここで表示され、ここに入れて使われるデータたちは合わせてアセットと呼ばれています。
    ディレクトリで分けたり名前を変更することもできます。各ファイルはアップロード時にIDが付与され管理されるため、同じ名前のファイル名をアップロードしても上書きアップロードにはならないことがあるので注意。

  5. インスペクター(INSPECTOR)
    ヒエラルキーやシーンで選択中のオブジェクトの設定を編集できます。
    3Dモデルのメッシュや衝突判定、物理制御に関するパラメータもここで定義することが可能。

コードエディター

基本的なGUIはこの5つで、他にコードエディターがあります。
コードエディターはメニューの下の方にあります。
Image may be NSFW.
Clik here to view.
D661465BE63B80816F3296B217A438C0.png

Image may be NSFW.
Clik here to view.
7D92D4DF895236818E45F1575C245ED8.png

コードエディターは普段使用されているであろうエディターと基本同じです。
JavaScriptのsnippetやコードのhighlightなども基本付いています。

基本ここでコードを書いて反映していきます。

Lauch実行

右上の再生ボタン的な矢印のボタンをクリックすると、Launch実行されます。
Image may be NSFW.
Clik here to view.
EFD619BA8AA8D149B421A6362044E895.png

Sceneの現状をLaunch実行してくれるため、開発しながら確認ができます。
Launch画面を開いたままにしておくと、Editor画面とライブリンクが持続した状態になり、Editor画面でScaleの数値などを変更するとリアルタイムでLaunch画面でもプレビューされます。
Image may be NSFW.
Clik here to view.
533E2936B0762A5D4A0D63B8A41B700E.png


新規プロジェクトの中身

新規から作ったプロジェクトなのにすでにモデルビュワーとして完成されています。
Assetsを確認するとModelデータやScriptsなどが入っています。

cube

CubeにはFBXという3Dモデルデータやマテリアル、テクスチャがあります。
PlayCanvasは3DモデルデータをインポートするとJSONデータに変換してくれます。
Image may be NSFW.
Clik here to view.
D26C58578614DFAB4C7B7714884E8FBC.png

cubemap

名前の通りCubeMapが入っており、メニュー下部のSETTINGS → RENDERINGとクリックしインスペクターを見るとSKyboxを指定する項目があります。
ここでCubeMapを設定することができます。
MipMapのLevelを設定できたり、Transparent Canvasを設定すれば背景透過Canvasも作ることができます。
Image may be NSFW.
Clik here to view.
8900D09B7FFEAC9BF01186932592504C.png

scripts

マウスの操作などはAssetsのscripts内にある以下のjsが対応しています。
- orbit-camera.js
- mouse-input.js
- touch-input.js
- keyboard-input.js

orbit-camera.js

オービットカメラを基盤ともなるscriptsです。
orbitとは軌道という意味で、ここでは特定のEntityを回転軸としてカメラが軌道を移動します。
中のコードを見るとわかりますが、distanceの最大最小値やpitchAngleの最大最小値を設定したり、inertiaFactorという慣性係数を指定したりと色々あります。
細かい調整などをしたい時にはこの中を読んで調整するのが良いでしょう

mouse-input.js, touch-input.js, keyboard-input.js

動かすためのmouseやtouchのinput情報をorbit-camera.jsに受け渡すscriptsです。
こちらも中身を見るとSensitivityなどを設定することができます。
mouseやtouchのイベントの取り扱いもこれを見ればわかってくると思いますので、この辺りのイベントを使いたい時には参考にするといいかもしれません。

これらのScriptsをCamera Entityに追加することで3Dモデルビュワーを実装しています。


新規プロジェクトからこれだけのものが用意されているので、コードを書く必要がほぼないです。
このまま使用しても良いですが、今回はこれを使ったWebページを作っていきます。

今回は下記のことを主にやっていきます。
- DOMを乗せる
- Vue.jsで3Dモデルを切り替える
- 切り替え時の動きを作る
- 3Dモデルごとに詳細ページを作る
- 詳細ページへの切り替えなどの処理を作る

上から順に進めていきます。

次回はDOMを乗せます

チュートリアル - Vue.jsでPlayCanvas製3Dモデルビュワーサイトを作る 2 / 4

前回はチュートリアルの前説と新しいプロジェクトを作るまでを行いました。
今回はPlayCanvasにDOMを追加するまでを行います。

DOMを乗せる

DOMを乗せるためのHTMLとCSSを作ります。

HTMLとCSSはPlayCanvasのAssetsから追加できます。
[+]ボタンをクリックし、該当する形式のファイルを選択します。
ファイル名は任意で構いませんが、以下にするとwebな感じですね。
- HTMLは「index」
- CSSは「style」

Image may be NSFW.
Clik here to view.
6398187B4ABCCE17A21401FE59135126.png

コードエディターからコードを書いて行きますが、
今回は事前に用意していますので、こちらをコピペして使用します。

[index.html]

<divid="app"><headerclass="header"><nav><ol><li><span>content01</span></li><li><span>content02</span></li><li><span>content03</span></li></ol></nav></header><mainclass="main"><divclass="container"><section>content01</section><section>content01</section><section>content01</section></div></main></div>

[style.css]

html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,main,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;outline:0;box-sizing:border-box;background:transparent;font-size:0;line-height:0;letter-spacing:0;vertical-align:baseline}html,body{font-size:100%;line-height:1}main,article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}navul,li{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}a{margin:0;padding:0;background:transparent;color:transparent;font-size:100%;vertical-align:baseline}ins{background-color:#ff9;color:#000;text-decoration:none}mark{background-color:#ff9;color:#000;font-style:italic;font-weight:bold}del{text-decoration:line-through}abbr[title],dfn[title]{border-bottom:1pxdotted;cursor:help}table{border-collapse:collapse;border-spacing:0}hr{display:block;height:1px;margin:1em0;padding:0;border:0;border-top:1pxsolid#cccccc}input,select{vertical-align:middle}input[type="text"]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;border:none;border-radius:0;outline:none;background:none}input[type="radio"],input[type="chackbox"]{display:none}input[type="radio"]:checked+label,input[type="chackbox"]:checked+label{background:transparent}select{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;outline:none;background:transparent}textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;resize:none;padding:0;border:0;outline:none;background:transparent}button,input[type="submit"]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;border:none;outline:none;background:transparent}input[type="submit"]::-webkit-search-decoration,input[type="button"]::-webkit-search-decoration{display:none}input[type="submit"]::focus,input[type="button"]::focus{outline-offset:-2px}html,body{width:100%;height:100%}body{color:#333;-webkit-backface-visibility:hidden;backface-visibility:hidden;font-family:Roboto,Arial,Helvetica,sans-serif;font-size:16px;line-height:1.5}a{outline:none;text-decoration:none}.wrapper__bg{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#246}main.main.color-base{color:#642}main.main.color-sub{color:#246}main.main.color-fontBase{color:#333}main.main.bgcolor-base{background-color:#642}main.main.bgcolor-base-1{background-color:rgba(102,68,34,0.1)}main.main.bgcolor-base-2{background-color:rgba(102,68,34,0.2)}main.main.bgcolor-base-3{background-color:rgba(102,68,34,0.3)}main.main.bgcolor-base-4{background-color:rgba(102,68,34,0.4)}main.main.bgcolor-base-5{background-color:rgba(102,68,34,0.5)}main.main.bgcolor-base-6{background-color:rgba(102,68,34,0.6)}main.main.bgcolor-base-7{background-color:rgba(102,68,34,0.7)}main.main.bgcolor-base-8{background-color:rgba(102,68,34,0.8)}main.main.bgcolor-base-9{background-color:rgba(102,68,34,0.9)}main.main.bgcolor-base-10{background-color:#642}main.main.bgcolor-sub{background-color:#246}main.main.bgcolor-sub-1{background-color:rgba(34,68,102,0.1)}main.main.bgcolor-sub-2{background-color:rgba(34,68,102,0.2)}main.main.bgcolor-sub-3{background-color:rgba(34,68,102,0.3)}main.main.bgcolor-sub-4{background-color:rgba(34,68,102,0.4)}main.main.bgcolor-sub-5{background-color:rgba(34,68,102,0.5)}main.main.bgcolor-sub-6{background-color:rgba(34,68,102,0.6)}main.main.bgcolor-sub-7{background-color:rgba(34,68,102,0.7)}main.main.bgcolor-sub-8{background-color:rgba(34,68,102,0.8)}main.main.bgcolor-sub-9{background-color:rgba(34,68,102,0.9)}main.main.bgcolor-sub-10{background-color:#246}main.main.bgcolor-fontBase{background-color:#333}main.main.bgcolor-fontBase-1{background-color:rgba(51,51,51,0.1)}main.main.bgcolor-fontBase-2{background-color:rgba(51,51,51,0.2)}main.main.bgcolor-fontBase-3{background-color:rgba(51,51,51,0.3)}main.main.bgcolor-fontBase-4{background-color:rgba(51,51,51,0.4)}main.main.bgcolor-fontBase-5{background-color:rgba(51,51,51,0.5)}main.main.bgcolor-fontBase-6{background-color:rgba(51,51,51,0.6)}main.main.bgcolor-fontBase-7{background-color:rgba(51,51,51,0.7)}main.main.bgcolor-fontBase-8{background-color:rgba(51,51,51,0.8)}main.main.bgcolor-fontBase-9{background-color:rgba(51,51,51,0.9)}main.main.bgcolor-fontBase-10{background-color:#333}main.mainsection{width:100%;padding:1rem}main.mainsection+section{margin-top:2rem}main.mainarticle{width:100%;height:100%}main.mainarticle+article{margin-top:1rem}main.mainh1{font-size:36px;font-size:2.25rem;line-height:1.2;letter-spacing:.05em}main.mainh2{font-size:32px;font-size:2rem;line-height:1.2;letter-spacing:.05em}main.mainh3{font-size:28px;font-size:1.75rem;line-height:1.2;letter-spacing:.05em}main.mainh4{font-size:24px;font-size:1.5rem;line-height:1.2;letter-spacing:.05em}main.mainh5{font-size:20px;font-size:1.25rem;line-height:1.2;letter-spacing:.05em}main.mainh6{font-size:16px;font-size:1rem;line-height:1.2;letter-spacing:.05em}main.mainp{font-size:12px;font-size:.75rem;line-height:1.2;letter-spacing:.05em}main.mainspan{font-size:12px;font-size:.75rem;line-height:1.2;letter-spacing:.05em}main.maina{color:#642;font-size:12px;font-size:.75rem;line-height:1.2;letter-spacing:.05em;transition:color.1s}main.maina:hover{color:#246}main.maina[target="_blank"]{text-decoration:underline}main.maina[target="_blank"]:after{content:"*"}main.mainulul,main.mainulol{padding-left:1rem}main.mainul>li:before{content:"・";position:absolute;top:0;left:0}main.mainol{counter-reset:item}main.mainolol,main.mainolul{padding-left:1rem}main.mainol>li:before{content:counter(item)".";counter-increment:item;position:absolute;top:0;left:0}main.mainli{position:relative;padding-left:1em;font-size:12px;font-size:.75rem;line-height:1.2;letter-spacing:.05em}main.main.tableWrap{width:100%}main.maintable{width:100%;border:1pxsolid#333}main.mainth,main.maintd{padding:.5em0;font-size:12px;font-size:.75rem;line-height:1.2;letter-spacing:.05em}main.mainth{text-align:center}main.maintd{text-align:center}main.maindt{display:inline-block;vertical-align:top;font-size:12px;font-size:.75rem;line-height:1.2;letter-spacing:.05em}main.maindt:after{content:":"}main.maindd{display:inline-block;vertical-align:top;font-size:12px;font-size:.75rem;line-height:1.2;letter-spacing:.05em}main.mainfigure{display:inline-block;vertical-align:top}main.mainfigureimg{display:block;width:100%;height:auto}main.mainfigurefigcaption{text-align:center}.container{max-width:1280px;margin:0auto}@mediascreenand(max-width:767px){body{font-size:8px;font-size:2.13333vw}main.mainsection+section{margin-top:4rem}main.mainarticle+article{margin-top:2rem}main.mainh1{font-size:21px;font-size:2.625rem}main.mainh2{font-size:19px;font-size:2.375rem}main.mainh3{font-size:17px;font-size:2.125rem}main.mainh4{font-size:15px;font-size:1.875rem}main.mainh5{font-size:13px;font-size:1.625rem}main.mainh6{font-size:11px;font-size:1.375rem}main.mainp{font-size:8px;font-size:1rem}main.mainspan{font-size:8px;font-size:1rem}main.maina{font-size:8px;font-size:1rem;transition:none}main.maina:hover{color:#642}main.mainli{font-size:8px;font-size:1rem}main.main.tableWrap{width:100%;overflow:auto;-webkit-overflow-scrolling:touch}main.main.tableWraptable{width:auto}main.mainth,main.maintd{padding:1em;font-size:8px;font-size:1rem}main.maindt{font-size:8px;font-size:1rem}main.maindd{font-size:8px;font-size:1rem}.container{max-width:100%}}body.is-open.pcCanvas{left:0!important;width:100%!important;pointer-events:none!important}body.is-open.header{left:-30%}body.is-open.detailBtn{opacity:0;pointer-events:none}body.is-open.main{width:100%;opacity:1;pointer-events:auto}canvas.pcCanvas{position:fixed!important;top:0!important;left:15%!important;z-index:-2!important;width:100%!important;height:100%!important;transition:width.3s,left.3s!important}.wrapper{position:fixed;top:0;left:0;z-index:1;width:0;height:100%;background-color:#ccc;transition:width.3s}.navBtn{display:none}.header{position:fixed;top:0;left:0;width:30%;height:100%;background-color:#aaa;transition:width.3s,left.3s}.headernav{padding:10%3%;height:100%;overflow:auto;-webkit-overflow-scrolling:touch}.headernavol{text-align:center}.headernavli{position:relative;padding:5%2%}.headernavli:after,.headernavli:before{content:"";position:absolute;top:0;left:0;z-index:-1;width:0;height:100%}.headernavli:after{background-color:#642;transition:width0.2s;transition-delay:.1s}.headernavli:before{background-color:#fff;transition:width.2s}.headernavlispan{position:relative;color:#fff;font-size:20px;font-size:1.25rem;line-height:1.2;word-break:break-all;cursor:pointer}.headernavlispan:before{content:"";position:absolute;left:50%;bottom:-5px;transform:translateX(-50%);width:0;height:1px;background-color:#fff;transition:width.2s}.headernavli+li{margin-top:10%}.headernavli:hoverspan:before,.headernavli.is-currentspan:before{width:120%}.headernavli.is-current:after,.headernavli.is-current:before{width:100%}.headernavli.is-currentspan:before{width:0}main.main{position:fixed;top:0;right:0;width:70%;height:100%;background-color:rgba(153,153,153,0.5);overflow:hidden;opacity:0;pointer-events:none;transition:width.3s,opacity.3s}.container{position:absolute;top:50%;right:-20px;width:40%;height:80%;padding-right:20px;overflow:auto;-webkit-overflow-scrolling:touch;transform:translateY(-50%)}.closeBtn{position:absolute;top:0;right:0;width:50%;height:10%}.closeBtn:before{content:"";position:absolute;top:50%;right:0;width:100%;height:2px;transform:translateY(-50%);background-color:#642}.closeBtn:after{content:"";position:absolute;top:50%;left:10px;width:2px;height:50%;transform:translateY(-50%);background-color:#642;transition:left.2s}.closeBtn:hover:after{left:30px}.detailBtn{position:fixed;bottom:10%;right:10%;width:15%;height:10%;transition:opacity.2s}.detailBtn:before{content:"";position:absolute;top:50%;right:0;width:100%;height:2px;transform:translateY(-50%);background-color:#642}.detailBtn:after{content:"";position:absolute;top:50%;right:10px;width:2px;height:50%;transform:translateY(-50%);background-color:#642;transition:right.2s}.detailBtn:hover:after{right:30px}@mediascreenand(max-width:767px){body.is-open.wrapper{height:100%}body.is-open.header{left:0}body.is-nav.wrapper{height:100%}body.is-nav.header{height:100%}canvas.pcCanvas{left:0%!important;z-index:1!important}.wrapper{position:fixed;top:0;left:0;z-index:2;width:0;height:0;overflow:hidden;background-color:#ccc;transition:width.3s}.navBtn{display:block;position:fixed;top:10px;right:10px;width:50px;height:50px;cursor:pointer}.navBtn:before{content:"";position:absolute;top:50%;left:0;width:100%;height:2px;background-color:#642}.navBtn:after{content:"";position:absolute;top:0;left:50%;width:2px;height:100%;background-color:#642}.header{width:100%;height:0;overflow:hidden;transition:width.3s,left.3s,height.3s}.headernavlispan{font-size:13px;font-size:1.625rem}main.main{width:100%}.container{right:50%;width:80%;height:80%;padding-right:0;transform:translate(50%,-50%)}.closeBtn{position:absolute;top:0;right:auto;left:0;width:50%;height:10%}}

HTMlとCSSは作りましたが、このままではPlayCanvasにDOMを乗せられていません。
DOMを乗せるためにScriptsを作り、以下のコードをコピペします。

[index.js]

varIndex=pc.createScript('index');Index.attributes.add("baseHtml",{type:"asset",assetType:"html"});// 登録したhtmlを取得Index.prototype.initialize=function(){varself=this;// this書き換えvarcanvas=document.getElementsByTagName("canvas")[0];// canvasを取得canvas.classList.add("pcCanvas");// canvasにclass名を指定varwrapper=document.createElement("div");// div作成wrapper.classList.add("wrapper");// 作成したdivにwrapperというclass名を指定wrapper.innerHTML=self.baseHtml._resources[0];// 事前に登録していたhtmlをwrapperに流し込みdocument.body.appendChild(wrapper);// bodyにwrapperを追加};

attributes.add();という言葉がありますが、これは属性設定ができます。
PlayCanvasのEditorから任意のデータを登録してScripts内で使用することができます。この機能は今後よく使う機能になってきます。
ここでは追加するためのHTMLを登録しています。

しかし、作成してもScriptsは正しく動作するわけではありません。
Entityに該当するScriptsを登録する必要があります。
今回はEntityのRootにScriptsを登録します。
以下の画像のように、Rootを選択したらインスペクターからADD COMPONENTをクリックし、Scriptsを選択します。

Image may be NSFW.
Clik here to view.
D02BF065344EDE22949CF183FC689D7E.png

SCRIPTSの項目が追加されたら、ADD SCRIPTをクリックし、indexを選択します。
Image may be NSFW.
Clik here to view.
45B2F5719EB760DC234C267E0F69D1A1.png

これでindex.jsがEntityのRootに登録されました。
確認しましたら、登録されたindex.jsの名前の右のParseをクリックします。
Parseは登録されているjsを再度リロードしてくれます。
リロードすることで、attributes.add();で追加した属性設定の項目が表示されます。
Image may be NSFW.
Clik here to view.
66E93CD1E5BD8CC7C3ADA9B9C1A1C099.png

baseHTMLという属性にはHTMLを選択することができますので、先ほど作成したHTMLを登録します。
これでHTMLは登録されましたが、CSSが登録されていませんので、HTMLと同じようにCSSも追加していきます。

setCss.jsというscriptを作って以下のコードをコピペします。

[setCss.js]

varSetCss=pc.createScript('setCss');SetCss.attributes.add("setCSS",{type:"asset",assetType:"css"});SetCss.prototype.initialize=function(){varcss=document.createElement("style");css.innerHTML=this.setCSS._resources[0];document.head.appendChild(css);};

scriptを作ったら、先ほどと同じようにEntityのRootにScriptを登録してParseしてCSSを登録します。
以下のようになりましたら問題ありません。
Image may be NSFW.
Clik here to view.
D2A69C9449814695FC7837E6324434B0.png

Lauchして確認してみますと、DOMが追加されたのが確認できたかと思います。

次からEntityを増やして、Vue.jsを使って3Dモデルを切り替えるようにします。

チュートリアル - Vue.jsでPlayCanvas製3Dモデルビュワーサイトを作る 3 / 4

前回はPlayCanvasにDOMを追加するまでを行います。
今回はVue.jsを使ってモデルの切り替わりなどを実装してきます。

Vue.jsで3Dモデルを切り替える

現在は3Dモデルが一つだけしか確認することができないので、複数の3Dモデルを追加します。
ヒエラルキーからEntityを追加していきます。

ヒエラルキー上部の[+]ボタンか右クリックからAdd Entity → Entityを選択します。
「New Entity」という名前で追加されたと思います。
Entitiesなど任意の名前に変更などして差別化します。
Image may be NSFW.
Clik here to view.
FF5DE61966CEFE3A9EF8C3B975EF3C66.png

Entitiesの中にModelを追加します。
ヒエラルキーのModelをDrag&DropでEntitiesの中に追加します。
Image may be NSFW.
Clik here to view.
349C24BFCC1590FD9F647DB8E1635BE6.png

3Dモデルを複数使用したいため、先ほどのModelを複製、またはPrimitiveなBoxなどを追加します。

複製はEntityを選択して、ヒエラルキー上部のDuplicate Entityをクリック。
Primitiveを追加するにはAdd EntityからPrimitiveを選択します。

上記の操作を行うと以下のようになります。

Image may be NSFW.
Clik here to view.
E7E882CB182B0D9C28A1E15B0AD9FE16.png

追加したモデルが重なり合っていますので、追加したEntities内のEntityのインペクターからEnabledのチェックを外し非表示にしておきます。
Image may be NSFW.
Clik here to view.
186CA2B76C6D962C83A1F0FAA34A5E5E.png

3Dモデルを追加できたら、次はVue.jsを使ってそれらを操作していきます。
Vue.jsを使うために、PlayCanvasにVue.jsのCDNを使います。
EXTERNAL SCRIPTSという項目がありますのでこちらから登録します。

CDN : https://cdn.jsdelivr.net/npm/vue

Image may be NSFW.
Clik here to view.
2CB0D3C88FDB9FF3D11497E3F963AC37.png

Vue.jsを書いていく前に、Vue.jsがわからない人のために簡単に説明をします。
Vue.jsはプログレッシブフレームワークと言われるライブラリとフレームワークの中間のもので、フレームワークとは異なり適所に少しずつVueを適用することができます。
詳しく知りたい方はVue.jsの公式ページから参照いただけたらと思います。
https://jp.vuejs.org/

Vueを使って切り替えるために、index.jsをいかに書き換えます。

[index.js]

varIndex=pc.createScript('index');Index.attributes.add("baseHtml",{type:"asset",assetType:"html"});// 登録したhtmlを取得Index.attributes.add("target",{type:"entity"});// 参照するEntityを登録Index.prototype.initialize=function(){varself=this;// this書き換えvarcanvas=document.getElementsByTagName("canvas")[0];// canvasを取得canvas.classList.add("pcCanvas");// canvasにclass名を指定varwrapper=document.createElement("div");// div作成wrapper.classList.add("wrapper");// 作成したdivにwrapperというclass名を指定wrapper.innerHTML=self.baseHtml._resources[0];// 事前に登録していたhtmlをwrapperに流し込みdocument.body.appendChild(wrapper);// bodyにwrapperを追加varnowEntity="";// どのEntityが選択されているか保管varapp=newVue({// Vue呼び出しel:'#app',// id名がappの要素を参照data:{// 使用するdataを登録clickFlag:false,// クリックされたかentities:self.target.children// 属性で追加したEntityの子どもを参照},methods:{// イベントハンドラ作成onEntityClick:function(target,index){// Entityを選択された時varv_self=this;// thisを保管if(nowEntity){nowEntity.enabled=false;// 今表示されているEntityを非表示に}target.enabled=true;// 選択したEntityを表示nowEntity=target;// 選択したEntityを保管}}});};

属性を追加したので、EntityのRootからindex.jsをParseします。
Parseしたら属性のtargetにEntitiesを登録します。
Image may be NSFW.
Clik here to view.
9EE1754EB2FFDD846248065ABA4D3F7E.png

Vueを使ってイベントハンドラを作っていますので、これをHTML側で使用します。
index.htmlをいかに書き換えます。

[index.html]

<divid="app"><headerclass="header"><nav><ol><liv-for="(entity,index) in entities"@click="onEntityClick(entity,index)"><span>{{entity.name}}</span></li></ol></nav></header><mainclass="main"><divclass="container"><section>content01</section><section>content01</section><section>content01</section></div></main></div>

ここで、index.jsで呼び出したVueのdataが確認できます。
entitiesというのが、index.jsで属性追加したEntityです。
それをv-forを使って一つずつ呼び出しています。
呼び出すli要素にはonEntityClickを@clickのクリックイベントで呼び出すように書き加えています。

これでLaunchを確認すると、先ほど設定した属性要素のEntitiesの子要素であるEntityの名前が並んでいます。
これらをクリックするとクリックしたモデルに変更されるのが確認できます。
Image may be NSFW.
Clik here to view.
EFD619BA8AA8D149B421A6362044E895.png

EditorからEntitiesにEntityを追加すると、PlayCanvasのリアルタイムレンダリングによりLaunchをリロードしなくても追加されます。
試しにEntitiesにPrimitiveを追加してみるとわかりやすいでしょう。


Vueを使って3Dモデルの切り替えができましたが、
パッと切り替わってしまうので見応えがありません。
そこで、切り替わる際にアニメーションを入れたいと思います。

切り替え時の動きを作る

Webでアニメーションといえばライブラリが数多く用意されています。
PlayCanvasにもTweenライブラリが用意されていて、これを使うことで現在の値から指定した値へアニメーションを行うことができます。

PlayCanvas Tween Library
https://developer.playcanvas.com/en/tutorials/tweening/

新しくtween.jsを作り、このTween Libraryのコードをコピペします。
書き換え終えたらOKです。
jsのインスペクターを見るとpreloadにチェックが入っています。
これにチェックを入れているとアプリケーションが読み込まれた時に一度ロードされます。
PlayCanvasに関わる記述以外を一度読み込みます。tween.jsはPlayCanvasに関わる記述がないために、Entityへの登録は必要が無くなります。

Image may be NSFW.
Clik here to view.
C51A4EC3EE5B960F9174B7735738EF0F.png

では、追加したTween Libraryを使ってアニメーションを追加していきます。
index.jsのVueの箇所を以下のコードに書き換えてください。

[index.js]

varIndex=pc.createScript('index');Index.attributes.add("baseHtml",{type:"asset",assetType:"html"});// 登録したhtmlを取得Index.attributes.add("target",{type:"entity"});// 参照するEntityを登録Index.prototype.initialize=function(){varself=this;// this書き換えvarcanvas=document.getElementsByTagName("canvas")[0];// canvasを取得canvas.classList.add("pcCanvas");// canvasにclass名を指定varwrapper=document.createElement("div");// div作成wrapper.classList.add("wrapper");// 作成したdivにwrapperというclass名を指定wrapper.innerHTML=self.baseHtml._resources[0];// 事前に登録していたhtmlをwrapperに流し込みdocument.body.appendChild(wrapper);// bodyにwrapperを追加varnowEntity="";// どのEntityが選択されているか保管varapp=newVue({// Vue呼び出しel:'#app',// id名がappの要素を参照data:{// 使用するdataを登録clickFlag:false,// クリックされたかentities:self.target.children// 属性で追加したEntityの子どもを参照},methods:{// イベントハンドラ作成onEntityClick:function(target,index){// Entityを選択された時varv_self=this;// thisを保管if(v_self.clickFlag)return;// クリックされて処理中かv_self.clickFlag=true;// クリック処理中self.target.tween(self.target.getLocalRotation()).rotate(newpc.Vec3(180,0,0),0.5,pc.QuadraticIn).on('complete',function(){// tweenでrotateをアニメーション completeで完了後呼び出しif(nowEntity){// Entityが保管されているかnowEntity.enabled=false;}target.enabled=true;// 選択したEntityを表示nowEntity=target;// 選択したEntityを保管self.target.tween(newpc.Vec3(180,0,0)).rotate(newpc.Vec3(360,0,0),1,pc.BounceOut).on('complete',function(){v_self.clickFlag=false;}).start();// 選択したEntityのアニメーション}).start();}}});};

今回はTweenを使ってRotateさせて180度開店したら選択したEntityに変わり、また180度回転して元に戻るようにしています。

このTweenのアニメーションはPositionやScale、マテリアルがあればその値も使うことができるので、色んな場で活用できる便利なライブラリです。

ここまで出来たら、せっかくDOMも追加してVueも入れているので
選択した3Dモデルの詳細スペックなどが見れるように新たにDOMが表示されるようにしたいと思います。

チュートリアル - Vue.jsでPlayCanvas製3Dモデルビュワーサイトを作る 4 / 4

前回はVue.jsでモデルを切り替えたりTweenを入れて動かしたりするまで行いました。
次は各モデルごとに詳細ページを作っていきたいと思います。

3Dモデルごとに詳細ページを作る

選択した3Dモデルごとに対応したDOMを表示させるようにします。

まずは3Dモデルの各Entityに載せたい情報が入ったHTMLを登録していきます。

今回は私の方で用意したHTMLを追加します。
中の記述は任意で変更してみてください。

[content01.html]

<article><h1class="color-base">3Dモデルビュワー01</h1><p>杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー杏マナー</p></article><article><h2class="color-base">プリミティブでも綺麗なものは作れる</h2><divclass="tableWrap"><table><tbody><tr><thclass="bgcolor-sub-5">Primitive</th><td>Box/Sphere</td></tr><tr><thclass="bgcolor-sub-5">Material</th><td>None</td></tr><tr><thclass="bgcolor-sub-5">Collision</th><td>0On</td></tr><tr><thclass="bgcolor-sub-5">RighidBody</th><td>On</td></tr></tbody></table></div></article><article><h3>プリミティブでも良いところはある</h3><ul><li><spanclass="color-base">プリミティブだから</span></li><ul><li><span>軽い</span></li><li><span>見栄えは気にしちゃいけない</span></li></ul></ul><ol><li><span>モデリングができなくても…</span></li><li><span>プリミティブでも頑張ろう…</span></li></ol></article><article><h2>宣伝</h2><p><ahref="https://pcpo.sabo.jp/3dwebsite/"target="_blank"><spanclass="color-base">3DWebSite</span></a><span>というPlayCanvas運営事務局が作った3DのWebサイト</span><br><span>その3DWebSiteが</span><ahref="https://webgl.souhonzan.org/entry/?v=1588"target="_blank"><spanclass="color-base">WebGL総本山</span></a><span>で紹介していただきました!</span></p></article><article><dl><dt>PlayCanvas運営事務局</dt><dd>キドユウタ</dd></dl></article>

切り替えた時に中のHTMLがちゃんと切り替わっているか判断しやすくするために、HTMLは複数作っておきます。
Image may be NSFW.
Clik here to view.
8E5577C0E75192DD24E3CA6408E41423.png

念の為、中の文章を変更するなどしておくと良いです。
Image may be NSFW.
Clik here to view.
758C54CCCE6B223844B68126749072AC.png

作成したHTMLをそれぞれEntityに当てはめて行きます。
そのために、当てはめる用のjsを作成します。

HTMLを追加するためだけなので、属性設定に登録できるようにします。

[addhtml.js]

varAddhtml=pc.createScript('addhtml');Addhtml.attributes.add("sethtml",{type:"asset",assetType:"html"});Addhtml.prototype.initialize=function(){};Addhtml.prototype.update=function(dt){};

該当Entityを選択して、ADD COMPONENTからScriptsを選択。
ADD SCRIPTから先ほど作成したaddhtml.jsを登録し、ParseしてからHTMLを登録します。

Image may be NSFW.
Clik here to view.
7D0BF2BF54FB39F095BF99773DDC6203.png

同じjsをテンプレート化して使いまわしたいというやり方がPlayCanvasのEditorではできます。
各々のEntityに登録ができるので、開発も少し楽ができますね。

index.jsに戻りまして、先ほど登録したHTMLを取得してVueを使ってindex.htmlに流し込みます。
ます、どのデータに登録したHTMLが入っているのか探す必要があります。

ここで以下のコードをindex.jsの一番下に追加します。

[index.js]

Index.prototype.swap=function(old){for(vark=0;k<old.target.children.length;k++){console.log(old.target.children[k]);}};

swapというのは、このscriptがホットリロードされた時に呼び出します。
Lauchをリロードせずともscriptで使いたい情報を探すのに活用できます。

ここで参照しているのは、Entitiesの子要素であるEntityを参照しています。
子要素のEntityの属性に取得したいHTMLが入っているのでこれを探します。

すると以下にたどり着けると思います。

[index.js]

Index.prototype.swap=function(old){for(vark=0;k<old.target.children.length;k++){console.log(old.target.children[k].script.addhtml.__attributes.sethtml._resources[0]);}};

これを使って追加していきます。
先ほどswapで確認したやり方を使ってHTMLのソースを取得し、それをVueに渡してあげます。

以下をコピペして書き換えます。書き加えたのは17-20行目と27行目です。

[index.js]

varIndex=pc.createScript('index');Index.attributes.add("baseHtml",{type:"asset",assetType:"html"});// 登録したhtmlを取得Index.attributes.add("target",{type:"entity"});// 参照するEntityを登録Index.prototype.initialize=function(){varself=this;// this書き換えvarcanvas=document.getElementsByTagName("canvas")[0];// canvasを取得canvas.classList.add("pcCanvas");// canvasにclass名を指定varwrapper=document.createElement("div");// div作成wrapper.classList.add("wrapper");// 作成したdivにwrapperというclass名を指定wrapper.innerHTML=self.baseHtml._resources[0];// 事前に登録していたhtmlをwrapperに流し込みdocument.body.appendChild(wrapper);// bodyにwrapperを追加varnowEntity="";// どのEntityが選択されているか保管vart_htmls={};// htmlを保管for(vark=0;k<self.target.children.length;k++){// Entitiesの子要素に登録されたHTMLを参照t_htmls[k]=self.target.children[k].script.addhtml.__attributes.sethtml._resources[0];// 登録されたHTMLのソースを取得}varapp=newVue({// Vue呼び出しel:'#app',// id名がappの要素を参照data:{// 使用するdataを登録clickFlag:false,// クリックされたかentities:self.target.children,// 属性で追加したEntityの子どもを参照contents:t_htmls// HTMLをVueに渡す},methods:{// イベントハンドラ作成onEntityClick:function(target,index){// Entityを選択された時varv_self=this;// thisを保管if(v_self.clickFlag)return;// クリックされて処理中かv_self.clickFlag=true;// クリック処理中self.target.tween(self.target.getLocalRotation()).rotate(newpc.Vec3(180,0,0),0.5,pc.QuadraticIn).on('complete',function(){// tweenでrotateをアニメーション completeで完了後呼び出しif(nowEntity){// Entityが保管されているかnowEntity.enabled=false;}target.enabled=true;// 選択したEntityを表示nowEntity=target;// 選択したEntityを保管self.target.tween(newpc.Vec3(180,0,0)).rotate(newpc.Vec3(360,0,0),1,pc.BounceOut).on('complete',function(){v_self.clickFlag=false;}).start();// 選択したEntityのアニメーション}).start();}}});};Index.prototype.swap=function(old){for(vark=0;k<old.target.children.length;k++){console.log(old.target.children[k].script.addhtml.__attributes.sethtml._resources[0]);}};

Vueに追加したcontentsにHTMLのソースが入っているので、これをindex.htmlに追加します。

以下にコピペして書き換えます。
14行目に先ほどのcontentsを展開して挿入しています。

[index.html]

<divid="app"><headerclass="header"><nav><ol><liv-for="(entity,index) in entities"@click="onEntityClick(entity,index)"><span>{{entity.name}}</span></li></ol></nav></header><mainclass="main"><divclass="container"><sectionv-for="(content,index) in contents"v-html="content"></section></div></main></div>

Launchからディベロッパーツールで確認してみますと、section要素に先ほどのHTMLが追加されているのが確認できると思います。

Image may be NSFW.
Clik here to view.
FD5FD5D5C71FD782A173D5DA5BAB2F20.png

表示するためのHTMLも追加できましたので、
次に詳細ページに切り替えるための処理を書いていきます。

詳細ページへの切り替えなどの処理を作る

各々の詳細が書いてあるHTMLを追加できたので、それを表示するための処理を作っていきます。

選択したEntityから詳細ページへ行くためのボタンを用意します。
また、詳細ページから前のページに戻るためのボタンも用意します。

[index.html]

<divid="app"><headerclass="header"><nav><ol><liv-for="(entity,index) in entities"@click="onEntityClick(entity,index)"><span>{{entity.name}}</span></li></ol></nav></header><divclass="detailBtn"v-show="detailBtnShow"@click="onDetailClick()"></div><mainclass="main"><divclass="closeBtn"@click="onCloseClick()"></div><divclass="container"><sectionv-for="(content,index) in contents"v-show="sectionNum == index"v-html="content"></section></div></main></div>

detailBtnとcloseBtnを追加すると合わせて、この後行うクリックイベントとv-showという表示非表示を処理するための記述も書いています。
これらを使うために、index.jsに処理を書いていきます。

index.jsのVueの箇所を以下に書き換えます。

[index.js]

varapp=newVue({// Vue呼び出しel:'#app',// id名がappの要素を参照data:{// 使用するdataを登録clickFlag:false,// クリックされたかdetailFlag:false,// 詳細を表示しているかdetailBtnShow:false,// 詳細ボタンを表示しているかentities:self.target.children,// 属性で追加したEntityの子どもを参照contents:t_htmls,// HTMLをVueに渡すsectionNum:"",// 何個目のEntityが表示されているか},methods:{// イベントハンドラ作成onCloseClick:function(){// 詳細を閉じるdocument.body.classList.remove("is-open");detailFlag=false;},onDetailClick:function(target){// 詳細を表示detailFlag=true;document.body.classList.add("is-open");},onEntityClick:function(target,index){// Entityを選択された時varv_self=this;// thisを保管if(v_self.clickFlag)return;// クリックされて処理中かif(!v_self.detailBtnShow)v_self.detailBtnShow=true;// 詳細ボタンを表示(初回のみ)v_self.clickFlag=true;// クリック処理中this.sectionNum=index;// 何個目のEntityが表示されているかself.target.tween(self.target.getLocalRotation()).rotate(newpc.Vec3(180,0,0),0.5,pc.QuadraticIn).on('complete',function(){// tweenでrotateをアニメーション completeで完了後呼び出しif(nowEntity){// Entityが保管されているかnowEntity.enabled=false;}target.enabled=true;// 選択したEntityを表示nowEntity=target;// 選択したEntityを保管self.target.tween(newpc.Vec3(180,0,0)).rotate(newpc.Vec3(360,0,0),1,pc.BounceOut).on('complete',function(){v_self.clickFlag=false;}).start();// 選択したEntityのアニメーション}).start();}}});

onDetailClickとonCloseClickのイベントハンドラを作成しています。
ここで詳細の表示非表示を行いますが、その表示非表示はclass名を参照しています。
htmlのbody要素に特定のclass名を持つときにCSSを使って表示非表示するようにしました。
v-showを使うことでもできなくはないですが、アニメーションを含めるならCSSが簡単と思いましてこちらを採用しています。

これで3Dモデルビュワーは完成していますが、もう一点、スマホ対応もさせておきたいと思います。

index.htmlとindex.jsのVueの箇所を以下に書き換え

[index.html]

<divid="app"><divclass="navBtn"@click="onNavClick()"></div><headerclass="header"><nav><ol><liv-for="(entity,index) in entities"@click="onEntityClick(entity,index)"><span>{{entity.name}}</span></li></ol></nav></header><divclass="detailBtn"v-show="detailShow"@click="onDetailClick()"></div><mainclass="main"><divclass="closeBtn"@click="onCloseClick()"></div><divclass="container"><sectionv-for="(content,index) in contents"v-show="sectionNum == index"v-html="content"></section></div></main></div>

[index.js]

varapp=newVue({// Vue呼び出しel:'#app',// id名がappの要素を参照data:{// 使用するdataを登録clickFlag:false,// クリックされたかnavFlag:false,// スマホ時のメニュー表示しているかdetailFlag:false,// 詳細を表示しているかdetailBtnShow:false,// 詳細ボタンを表示しているかentities:self.target.children,// 属性で追加したEntityの子どもを参照contents:t_htmls,// HTMLをVueに渡すsectionNum:"",// 何個目のEntityが表示されているか},methods:{// イベントハンドラ作成onNavClick:function(){//メニューボタン押すif(!this.navFlag){// メニュー表示this.navFlag=true;document.body.classList.add("is-nav");}else{// メニュー非表示this.navFlag=false;document.body.classList.remove("is-nav");}},onCloseClick:function(){// 詳細を閉じるdocument.body.classList.remove("is-open");detailFlag=false;},onDetailClick:function(target){// 詳細を表示detailFlag=true;document.body.classList.add("is-open");},onEntityClick:function(target,index){// Entityを選択された時varv_self=this;// thisを保管document.body.classList.remove("is-nav");if(v_self.clickFlag)return;// クリックされて処理中かif(!v_self.detailBtnShow)v_self.detailBtnShow=true;// 詳細ボタンを表示(初回のみ)v_self.clickFlag=true;// クリック処理中this.sectionNum=index;// 何個目のEntityが表示されているかself.target.tween(self.target.getLocalRotation()).rotate(newpc.Vec3(180,0,0),0.5,pc.QuadraticIn).on('complete',function(){// tweenでrotateをアニメーション completeで完了後呼び出しif(nowEntity){// Entityが保管されているかnowEntity.enabled=false;}target.enabled=true;// 選択したEntityを表示nowEntity=target;// 選択したEntityを保管self.target.tween(newpc.Vec3(180,0,0)).rotate(newpc.Vec3(360,0,0),1,pc.BounceOut).on('complete',function(){v_self.clickFlag=false;}).start();// 選択したEntityのアニメーション}).start();}}});

navBtnという要素を追加して、onNavClickのイベントハンドラを呼ぶことでスマホ時でもメニューを開いて操作することができます。
ここでもbody要素にclass名のis-navを付与することでメニューの表示非表示を制御しています。
他にも色んな方法はありますので、試してみると良いでしょう。

これで、本チュートリアルの制作は終了です。

最後に今回作ったプロジェクトをPublishして終わりにしたいと思います。

Publishして誰でも見れるように公開!

プロジェクトを作成してビルドまでやってくれるのもPlayCanvasのすごいところ。

ここまで作成したものをPlayCanvasの公開環境に公開してみましょう。

左のメニュー、またはシーンの左上のManage Scenesから以下のPublish画面を開きます。
Image may be NSFW.
Clik here to view.
B3EAE2C667F0D901564A3E07AA17C432.png

PUBLISH TO PLAYCANVASを選択して、一番下のPUBLISH NOWをクリックしてビルドは完了です。
URLでPC、スマホ、他ユーザーにもログインすることなく共有することができます。
Image may be NSFW.
Clik here to view.
6CC3E7B70E347147E79D8F137A479F5B.png

以上でチュートリアルの終了です。
おつかれさまでした!


PlayCanvasはゲームエンジンでありますが、こんな感じでwebサイト的なものとして使えちゃいます。
WebGLで3Dコンテンツを使いたいけどうまく扱えない人とかにはとても良いものな気がします。

PlayCanvas、使いましょう!

-

Gulp環境構築にて「AssertionError [ERR_ASSERTION]: Task function must be specified」の原因はgulp v4にバージョンアップした事による仕様変更

始めての投稿です。
1. エラー内容、背景
2. 環境
3. 解決方法
4. 原因
の順に記載して行きます。

1. エラー内容、背景

gulpにてSassを自動コンパイルしたかったため、Homebrew、Node.js、gujpをインストール。
gulp自動監視を起動しようと、コマンド実行すると、

qiita.rb
'AssertionError [ERR_ASSERTION]: Task function must be specified'

と言うエラーが。

2. 環境

mac

  • npm 6.7.0
  • Node.js 12.13.0
  • CLI 2.2.0
  • gulp 4.0.3

3. 解決方法

Node.jsgulpのバージョンを下げる。

  • Node.js 12.13.0
  • gulp 4.0.3

  ↓

  • Node.js 11.15.0
  • gulp 3.9.1

Node.jsのバージョンの下げ方は、

qiita.rb
$npminstall-gn

を実行した後に、

qiita.rb
$sudon11.15.0

参考:Node.jsのバージョンを管理するライブラリ「n」

gulpのバージョンの下げ方は、

qiita.rb
$sudonpminstall--save-devgulp@3.9.1

4. 原因

①gulpのバージョンが4に上がったことにより、gulpfile.jsのgulp.task()の引数の指定方法が変わった。
 gulp v4へのバージョンアップによる変更点などはこちらのページがわかりやすくまとめられています。
 gulp v4.0.0がプレリリース! 移行方法と変更点まとめ
②gulp v3とNode.js v12の組み合わせは動かない
 gulp v3とNode.js v12で実行した場合、

qiita.rb
puts'ReferenceError: primordials is not defined'

 と言うエラーが。
 すみませんが、v12ではなぜ動かないのかはわからず、以下のページを参考にし、11.15.0だと動くことを確認しました。
 参考:ReferenceErrorを修正する方法

以上、AssertionError [ERR_ASSERTION]: Task function must be specifiedの解決方法でした。

django 画像をアップロードする画面を作る

env

python3.6.8
win10

はじめに

今回はdjangoで画像をアップロードする画面を制作します。以下手順でdjango上の画像をアップロードし表示する事ができます。それぞれの詳しい説明やトリッキーな設定方法などは他者に任せて、この記事ではなるべく記事をご覧頂いていたみなさんに早く 正確に 簡単に django上で画像をアップロードして頂く事に主眼を置きました。そのためweb閲覧ユーザの殆どから求められていないわかりずらい説明は一切省いてます。進めていくうちに言葉足らずな箇所など出てくるかと思いますが乱筆故予めご了承頂ければ幸いです。それではいきましょう。

いまからつくる画面

Image may be NSFW.
Clik here to view.
011.png

写真をアップロードする画面をつくります。上記イメージのように実際にアップロードしたら表示させてみましょう。

agenda

以下ステップで画像をアップデートする画面を構築する事が出来ます。項目が多く慣れるまで時間がかかりますが慣れてしまえば5分前後で画面を作る事が出来ます。

ライブラリインストール
メディア用ディレクトリの追加
URLの設定
モデル  👈👈👈👈👈 設定済の場合はこのセクションから
フォーム
ビュー
テンプレート
マイグレーション
管理ツール
参考

ライブラリインストール

django上で画像を扱うにはpillowというライブラリが必要ですので下記のコマンドでインストールしてください。

pip install pillow

settings.pyメディア用ディレクトリの追加

画像の保存先を指定する為にsettings.pyに以下のように追加・修正します。

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

指定したのですから実際にフォルダも作成しましょう。プロジェクトフォルダの直下に「media」という名前のフォルダを作成します。もしわからなければmanage.pyを探してください。そこと同じディレクトリ階層にmediaというフォルダを作成してください。ここに画像がアップロードされます。

URLの設定 プロジェクトのurls.py

urls.pyはアプリケーションとプロジェクトと2つある場合がありますね。今回はプロジェクトのurls.pyの末尾に以下の設定を追加します。

プロジェクトのurls.py

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

敢えて難しい言い方で説明するとディスパッチャの設定(urls.py)にメディアファイル公開用のURLを追加します。

アプリケーションのurls.py

    path('model_form_upload/', views.model_form_upload, name='model_form_upload'),

当たり前ですがプロジェクトとアプリケーションを間違えると動かないのでよく確認してください。

モデル

さて、まず画像を扱うためのモデルを書きましょう。様々な書き方がありますが私はdb設計をしたらまずモデルを書きます。

from django.db import models

class Document(models.Model):
    description = models.CharField(max_length=255, blank=True)
    photo = models.ImageField(upload_to='documents/', default='defo')👈👈👈👈👈
    uploaded_at = models.DateTimeField(auto_now_add=True)

upload_toを’documents’にしておくことで、/media/documentsに画像が保存されます。mediaは、そう先程あなたが作成したフォルダですね。そこの中に画像が格納されるようになったという事です。

フォーム

from django import forms
from .models import Document👈👈👈👈👈


class DocumentForm(forms.ModelForm):
    class Meta:
        model = Document
        fields = ('description', 'photo',)

from .models import Document を書くのを忘れないようにしてください。👈👈👈👈👈

ビュー

from django.shortcuts import render, redirect
from .forms import DocumentForm👈👈👈👈👈
from .models import Document👈👈👈👈👈


def index(request):
    if request.method == 'POST':
        form = DocumentForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
            #return redirect('apprication:model_form_upload')👈👈👈👈👈
            return redirect('apprication:index')👈👈👈👈👈


    else:
        form = DocumentForm()
        obj = Document.objects.all()

    return render(request, 'apprication/model_form_upload.html', {👈👈👈👈👈
        'form': form,
        'obj': obj
    })

from .forms import DocumentForm
from .models import Document
を書くのを忘れないようにしてください

また

return redirect('apprication:model_form_upload')

と記載がありますね。これは/model_form_uploadへ飛ばすという意味です。例えばページをトップページに遷移させたい時は以下のように設定します。※indexがtopなら

return redirect('apprication:index')

既存のままページ遷移させたくない時は以下のように設定してください。ページ遷移はせずにアップロードした文字や画像が表示されるはずです。またこのタイミングでmediaフォルダに画像がアップロードされます。

#return redirect('apprication:model_form_upload')

さてredirectの次はrenderです。

return render(request, 'apprication/model_form_upload.html', {

バリデーションチェックが通った時のどのhtmlへ向かうか指定します。model_form_upload と apprication/model_form_upload.html はあなたの環境に合わせて変更してください。書き方が違う事に注意してください。

テンプレート \templates\apprication\model_form_upload.html

題名の箇所にhtmlを配置します。

<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Upload</title>
    </head>
    <body>

        <form method="post" enctype="multipart/form-data">
            {% csrf_token %}
            {{ form.as_p }}
            <button type="submit">Upload</button>
        </form>

     {% for item in obj %}
    <tr>
        <td>{{ item.description }}</td>
        <td><img src="{{ item.photo.url }}" width="100" height="100"/></td>
        <td>{{ item.uploaded_at }}</td>
    <tr>
    {% endfor %}
    </body>
</html>

マイグレーション

以下コマンドでdb構成を再定義します。

python manage.py makemigrations
python manage.py migrate
python manage.py runserver

実装の確認

まず以下URLをたたきましょう。

http://127.0.0.1:8000/model_form_upload/

画面が表示されたら適当に文章や画像を入力してボタンを押して

Image may be NSFW.
Clik here to view.
001.png

正常にあなたが作成したmediaフォルダの中に

Image may be NSFW.
Clik here to view.
002.png

画像がアップロードされていれば確認OKです。何回か実施してみましょう。のちに管理ツールで画像を削除する事も可能です。

Image may be NSFW.
Clik here to view.
0123.png

管理ツール

管理ツール上に項目を登録すれば画像を削除する事ができます。http://localhost:8000/admin で管理者権限でログインします。今回は管理者ツールでデータを作成し、web上で実際に表示できるか確認をしてみましょう。

admin.py

from django.contrib import admin
from .models import Document

# Register your models here.
admin.site.register(Document)

管理ツールでデータを追加する事も可能です。
Image may be NSFW.
Clik here to view.
011.png

実際のサイトにいくと・・・・

Image may be NSFW.
Clik here to view.
011.png

先程管理者ツールで入力したデータが格納されている事がわかりました。

番外編

ユーザを作っていない場合には以下でユーザを作成しましょう。

python manage.py createsuperuser

ユーザ名(admin)
メールアドレス(admin@no.com)
パスワード(適当)


[html css]簡易リンク集の作成方法

簡易リンク集の作成方法

以前仕事中によく使われていたので、bak&紹介します

リンク集.htm
<html><head><title>◆□■◇リンク集</title><script language="JavaScript"><!--functionrsz1(){alert("ブラウザの最適サイズを自動調整にしようと思ったが、未対応")//alert(window.outerWidth);//alert(window.outerHeight);//window.resizeTo(1226, 714);//window.outerWidth=1226;//window.outerHeight=714;}--></script></head><bodystyle="backgound-color:#CCCCCC;"><tablestyle="width:100%;border:0px;margin:0px 0px;padding:0px 0px;"><tr><tdstyle="width:12%;font-size:10;">2019/11/01~随時更新中</td><tdstyle="width:28%;font-size:10;color:white;"><marqueebehavior="scroll"bgcolor="red">最優先</marquee></td><tdstyle="width:28%;font-size:10;color:red;"><marqueebehavior="scroll"bgcolor="yellow">優先</marquee></td><tdstyle="width:28%;font-size:10;color:white;"><marqueebehavior="scroll"bgcolor="green">★★★ 勤務表:毎月10日、20日に注意</marquee></td><td=style="width:4%;text-align:right;"><inputstyle="font-size:1;"type="button"value="..."onclick="rsz1();"title="リサイズ"/></td></tr></table><hr><divclass="divContents"><spanclass="spanPartTitle">◆□Shareサーバー:ABC</span><tableclass="tbl"><tr><thclass="th">標題1 </th><tdclass="td1"><ahref="link1"target="_blank"title="link1_title">linkComment1</a><br></td><tdclass="td2">link備考1</td></tr></table><spanclass="spanPartTitle">■◇ローカル資料</span><tableclass="tbl"><tr><thclass="th">標題2 </th><tdclass="td1"><ahref="link2">linkComment2</a><br></td><tdclass="td2">link備考2</td></tr></table></div><hr><divstyle="text-align:right;font-size:10;">@個人作業用。効率的に</div></body><style>.tbl{width:90%;}.th{background-color:#AAAAAA;color:white;width:30%;align:center;font-size:13;}.td1{background-color:#AAAAAA;width:60%;font-size:12;}.td2{background-color:#AAAAAA;width:10%;font-size:10;}.divContents{background-color:#BBBBBB;height:520px;overflow-x:hidden;overflow-y:scroll;padding:5px;boder:0pxsolid#000;}.spanPartTitle{font-size:22;}</style></html>

※ブラウザはIEを推薦。
※ブラウザの最適サイズをJsで自動調整にさせようと思ったが、良い方法をまだ

input要素だけでcheckboxをカスタムしたい

<inputtype="checkbox"/>
input[type='checkbox']{appearance:none;outline:none;display:block;position:relative;text-align:center;cursor:pointer;width:28px;height:28px;margin:0auto;}// チェックボックスのデザインinput[type='checkbox']::before{display:block;position:absolute;content:'';width:28px;height:28px;background:#fff;border:solid2px#01a2c1;border-radius:3px;}// チェックボックスの背景をチェック後に変更input[type='checkbox']:checked::before{background-color:#01a2c1;}// チェックマークのデザインinput[type='checkbox']::after{display:block;content:'';position:absolute;left:10px;top:5px;width:8px;height:16px;border-right:3pxsolid#fff;border-bottom:3pxsolid#fff;transform:rotate(45deg);// チェックしてないときは隠すopacity:0;}// チェックするとチェックマークの透明化を解除input[type='checkbox']:checked::after{opacity:1;}

完全レスポンシブデザインにする必要があるかどうか

レスポンシブデザインでwebサイトを開発する場合、スマホ用とPC用でデザインをあてることになる。

ここで、PCにおいて完全にレスポンシブにする必要はない場合が多い。

わざわざ画面幅を変えた時の挙動を考えずとも、一番外のレイヤーに
min-width: 1000px;
のように書いてしまえば良い。

初めてレスポンシブデザインを開発する場合などは流れでちゃんと作ろうとしてしまうが、そもそもその必要があるのか、視野が狭くなっていないかなどは常に気を付けていきたい。

画像などに設定したリンクが効かない

position: absolute;
で位置を変えた画像に設定したリンクが効かなかった。

z-index: 99;
で解決した。

画像にかかわらず、クリック・ホバー時の動きが全く反応がない場合、見えない何かが上に被さってる場合が多い。今回は、近くの要素のpaddingが画像を覆っていたため反応がなかったっぽい。

マウスホバー時に border-bottom が消える奇妙な現象がEdgeでのみ起こったという話

その日、私はとある企業向けのwebアプリのブラウザ検証を行っていた

ブラウザ検証と言っても ie11 と Edge で動けばいいよ~程度のモノだ。

私はまず Edge を立ち上げ、開発サーバのURLを叩き、意気揚々とログインした。
トップページのヘッダに置かれた三本線のボタンを押すと、縦に並べられたメニューが画面左側からフェードインしてくる。
いわゆるハンバーガーメニューだ。

(´-`)oO(とりあえず上から順に一通りのメニューを開いてみるか)

私はメニューの数を数えるように、何気なくマウスをメニューの上でゆっくりと滑らせた。
そのとき事件は起こった。

あれ……?メニューの表示が何かちらつくぞ……?

メニューに引かれた borderが、マウスの動きに合わせて消えたり現れたりしていたのだ。
間違ってもそんな仕様ではない。
おかしい、以前は普通だった気がするのだが……?

Edgeでも動作確認してたハズだけどな……

下はこの現象を再現出来るコードだ。

See the Pen NWWwooe by 8810hayate (@K-Z) on CodePen.

と言っても、この現象は特定の条件によって発生するものなので、この画面を触ってみても、この記事をご覧の方の環境では現象が再現しないかもしれない。

現象が発生したときのスクショを併せて貼っておく。

Image may be NSFW.
Clik here to view.
メニュー.png

本来は全てのメニューに 1px のボーダーボトムが設定されていたのだが、画像では消えてしまっているのがお分かりいただけると思う。

そして調査が始まる

とりあえず、IE、Edge、おまけでChromeを立ち上げてメニューの上で同じようにマウスを動かしてみる。

……現象が再現するのは Edge だけだ。

じゃあ Edge の不具合?なのか??

次に隣の後輩に自分の画面を見せる。
説明して、後輩の端末でも同じことが起きるか確認してみた。

すると……
再現しない!!

これはビンゴだ、しめしめ。
OSやブラウザのバージョンを後輩と比較して確認してみる

……しかし。

全く同じだ。

私がテストしていた端末、後輩の端末のOS、ブラウザのバージョンは以下の通りで、全く同じだったのだ。

OS Windows 10 Pro
バージョン 1809
OSビルド 17763.775

IE バージョン 11.775.17763.0
IE 更新バージョン 11.0.147

Microsoft Edge 44.17763.771.0
Microsoft EdgeHTML 18.17763

Google Chrome 78.0.3904.70

OS や Edge のバージョンによるものではない……。

発生条件は何だ!?

私と後輩の環境で何が違うのか……バージョンじゃないとすると何かしらの設定か……?
特別な設定なんて何か入れたっけ……?

そのとき、後輩が私の画面を見ながら不思議そうに呟いた。

「先輩の画面、少し文字が大きいですね」
「え?」

原因は拡大率?

私が自分の端末の Edge で拡大率を確認すると 105%、後輩のところは 100% だった。

まさかそんなことで……?
じゃあ、拡大率を変えれば他のブラウザでも現象が再現するのか……?
私は後輩に礼を言って、自分の端末に向き直り、改めてそれぞれのブラウザで拡大率を変えながら確認する。

結果……

IE11
拡大 80% 85% 90% 95% 100% 105% 110% 115% 120% 125%
いずれも現象再現なし。

Microsoft Edge
拡大 80%、85%、105%、110%
現象再現。
拡大 90%、95%、100%、115%、120%、125%
現象再現なし。

Google Chrome
拡大 80% 90% 100% 110% 125%
いずれも現象再現なし。

やっぱ Edge だけやんけ!なんやこれ!

border-top の場合は発生しない!?

理屈はよく分からないが、現象が発生する環境はある程度絞られた。
なら、どうコードを修正すればこの現象を避けられるのだ?

私はコードを弄りながら試行錯誤を繰り返した。
余分なモノを少しずつ削りながら、現象が再現するか確認しては元に戻す。

そうして分かったことは、

border-bottom

は消えたり現れたりするが、

border-top

ならセーフだということだ。

Edge を使用出来る環境の方は、先ほど上に載せたコードで発生していた現象が、この下に載せたコード(スタイルシートのborder-bottomをborder-topに書き換えたもの)で発生しないことがおそらく確認出来ると思う(『思う』と言っているのは、もしかしたらOSやブラウザのバージョンで再現しないものもあるかもしれないからである。そこまでの検証は出来ていないので)。

See the Pen RwwjdPy by 8810hayate (@K-Z) on CodePen.

また、調査を進めるうちに、border-bottom であっても、メニューの float を解除すれば現象が再現しないことも分かった。

完全に解決したとは言えないが

いや、でも俺は bottom にボーダーを引きたいし、float にしたいんだよ。
どうしろっての?

頭を捻って無理やり解決した。
border-bottom はやめて、border-topにする。
ただしこれではボーダーの位置が変わってしまうので、一番上のメニューだけボーダーを消し、一番下のメニューの下に架空の高さ1pxのメニューをでっちあげた。

See the Pen oNNoVjG by 8810hayate (@K-Z) on CodePen.

おわりに

書いておいて何だが、これで表面上は解決できるのだとしても、ある種のバッドノウハウじゃないかと思う。
もっといい解決は何かないものか……

Viewing all 8524 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>