- 投稿日:2020-08-24T23:42:55+09:00
FullCalenderの使い方まとめメモ(v5.3.0)
FullCalenderを使用した備忘録をまとめます。
ダウンロード
https://fullcalendar.io/docs/getting-started
今回はPre-built Bundlesのzipファイルをダウンロードして使用
Download: fullcalendar-5.3.0.zipダウンロードして展開したlibディレクトリの中身を使用するプロジェクトに移す
カレンダーの表示
https://fullcalendar.io/docs/initialize-globals
こちらを参考に記載calender.html<!DOCTYPE html> <html lang='en'> <head> <meta charset='utf-8' /> <link href='fullcalendar/main.css' rel='stylesheet' /> <script src='fullcalendar/main.js'></script> <script src='calender.js'></script> </head> <body> <div id='calendar'></div> </body> </html>calender.jsdocument.addEventListener('DOMContentLoaded', function() { var calendarEl = document.getElementById('calendar'); var calendar = new FullCalendar.Calendar(calendarEl, { initialView: 'dayGridMonth' // オプション (月表示) }); calendar.render(); // カレンダーの初期化(再レンダリング) });
calernderというidを振った場所にカレンダーが表示される
変数calenderを初期化(レンダリング)することで表示できる変数calenderの第2引数のオブジェクトにオプションを指定でき、
カレンダーの設定などが行えるjQueryは削除されたため、以前のバージョンで使用できた
下記のような記載方法では表示できない$('#calendar').fullCalendar()Alpha Release: jQuery Removal | FullCalendar Blog
https://fullcalendar.io/blog/2018/04/alpha-release-jquery-removalバージョンによって記載方法や指定方法が大きく異なるので注意
オプション
ドキュメントをみて使用しそうなオプションを列挙
- タイムゾーン
timeZone: 'Asia/Tokyo',
- ロケール
locale: 'ja',
- 週の初めの日を設定
firstDay: 0,0:日曜始まり、1:月曜始まり
(デフォルトは日曜始まり)
- カレンダー上部のタイトルやボタンを設定
headerToolbar: { left: false, center: "title", right: "today prev,next" },左:なし(なしの場合はfalse)
中央:タイトル
右:今日へ戻るボタン、先月ボタン、次の月ボタン
- 設定したボタンのテキストを設定
buttonText: { today: '今月', },
- カレンダーに表示するイベントを設定
events: [ { id: 'a', title: 'my event', start: '2018-09-01' } ]jsonを指定できる
そのためPHP等でjsonを返すような処理を作成し、下記のようにURLを指定することもできるevents: '/hoge/list',
- 予定をクリックして動作する処理を設定
eventClick: function(info) { // 処理 },
- カレンダーの日付の枠内をクリックして動作する処理を設定
dateClick: function(info) { // 処理 },
js初心者で言い回し等が間違っている箇所があると思います…
書き方や言い回しなどがおかしい部分があれば教えていただけると幸いです!
- 投稿日:2020-08-24T22:53:16+09:00
【firebase】firebase.関数名() is not a function がでた際に確認する事【Javascript】
はじめに
特に難しい内容ではないのですが躓いたのでメモ。
何かしらの記事を参考にしながらfirebaseの接続を行った際に
firebase.firestore() is not a function firebase.storage() is not a function firebase.auth() is not a function等のエラーが発生したことがありませんか?
今回はこのエラーが出現した際に行う対処法の紹介です。
1.Nuxtでモジュールとして読み込む場合
モジュールとして読み込む場合、firebase.js等のファイルを作成して、以下の記述をするかと思います。
import * as firebase from 'firebase';実は、firestore等を利用する際はこれだけでは記述が不足しています。
import * as firebase from 'firebase'; // 新規追加 import 'firebase/auth'; import 'firebase/firestore';のように、使いたいサービスのモジュールを新しく読み込む必要があります。
2.Nuxtでモジュールとして読み込む場合(firebase , firebase-admin両方使用している場合)
本来であれば、両方が同じプロジェクトに存在する事はない?のですが、様々な理由で使用せざるを得なくなる場合があると思います。
ですが、fierbase,firebase-adminの両方を使用しているプロジェクトの場合、
npm i
でインストールするタイミングによりエラーが発生するバグがあるそうです。手順としては、
1. node_modulesの削除
2. package-lock.jsonの削除
3.npm i
を実行以上の事を行えば、firebaseとfirebase-adminが正しい順序でインストールされ、エラーが発生しなくなるかもしれません。
参考:https://github.com/firebase/firebase-js-sdk/issues/752
3.CDNから配信されたものを使用する場合
1.と同じです。
<script src="https://www.gstatic.com/firebasejs/7.19.0/firebase-app.js"></script>上記のようにCDNを用いて.vueファイル等で直接firebaseを読み込んでいる場合、
<script src="https://www.gstatic.com/firebasejs/7.19.0/firebase-app.js"></script> // 新規追加 <script src="https://www.gstatic.com/firebasejs/7.19.0/firebase-auth.js"></script> <script src="https://www.gstatic.com/firebasejs/7.19.0/firebase-database.js"></script>firebase-app.jsのみだと、 authやfirestoreが使用出来ません。
なので、使いたいサービス(firestore,auth,function等)を明示的に読み込む必要がありました。参考:https://qiita.com/sota0726/items/e9ab104df3ec77d00f8e
4.もしかして
1.と 2. を実施しても治らない場合、firebaseがインストールされていない可能性があります。
私はこれでした。
当たり前にインストールしているだろうと思って確認しなかったのですが、案外気づかないものですね。packeage.jsonのdevDependencies またはdependenciesの中に,
"firebase": "^7.19.0", (バージョンは異なります)と記述はありますか?
- 投稿日:2020-08-24T22:24:59+09:00
Mixed Content: The page at 'https://〜〜〜' was loaded over HTTPS,というエラーが出た時の対処
あらすじ
サイトに
jQuery
を拡張するライブラリ(プラグイン)の一種でjQuery UI
を利用しダイアログ
を表示しようとしたときに出たエラーです。
HTML
のheader部分
にライブラリを読み込むため、以下のような記述をプラスし<link type="text/css" rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/cupertino/jquery-ui.min.css" />今回は
heroku
を利用してデプロイしました。エラー内容
そうするとクリックしても
ダイアログ
がうまく表示されず。検証ツールでコンソールを見てみると赤文字でエラーの表示がでてました。
Mixed Content: The page at 'https://home-game-center.herokuapp.com/' was loaded over HTTPS, but requested an insecure stylesheet 'http://code.jquery.com/ui/1.12.1/themes/pepper-grinder/jquery-ui.css'. This request has been blocked; the content must be served over HTTPS.「
http
からのリンクなんで読み込めませんよ」とのことのようです。実際そのページだけ必要なものが読み込めていない。調べてみると、『
HTTPS
とHTTP
の混合サイトで外部リンクが読み込めない』みたいです。解決策
解決策としては
『(’https:/ or ‘http:/)は省略できる』
とのことなので記述を以下のようにしました。<link type="text/css" rel="stylesheet" href="//code.jquery.com/ui/1.10.3/themes/cupertino/jquery-ui.min.css" />
href=
のあとのhttp:
を省略して再度デプロイすると本番環境でも正常にダイアログが出て解決しました!しかし今度はローカルではうまく動作せずになりましたが(涙)
参考
リンクの http: や https: を省略して現在のプロトコルでリンク先にアクセスさせる
・https://qiita.com/arai-wa/items/d9b73539898d658b58c5
- 投稿日:2020-08-24T21:58:12+09:00
JavaScriptのクラスと継承
JavaScriptはこれだけ広く使われている言語にしては、意外と今回のような情報は見つからなかったりします。
まずはおさらい。
- 全てはオブジェクトである。
- 継承はプロトタイプチェーンによって行う。
- 関数名にnewをつけて呼び出すと、その関数はコンストラクタと呼ばれ、新たなオブジェクトを作る。
このへんでつまづく人はいないと思うのです。
でも、具体的にBaseというクラスをDerivedというクラスが継承し、そのオブジェクトをnewで作ったという場合に、プロトタイプチェーンがどのように構築されるかをはっきり説明できる人は少ないような気がします。
文章でだらだら説明しても必ずわかりにくくなるので、結論から図で描いてしまうとこうなっているわけです。コンストラクタとクラスオブジェクトは別物で、 constructor と prototype というフィールドで相互参照しているという点が初心者にはわかりにくいと思います。
なお、__proto__
というフィールドは内部的なプロトタイプチェーンを実装するための参照であり、コードから直接アクセスしてはいけません。このような継承関係が構築されていることは、下記のようなコードで確認できます。
class Base{ } class Derived extends Base{ } const base = new Base() const derived = new Derived() console.log(derived.__proto__.__proto__ === base.__proto__) console.log(base.constructor === Base) console.log(derived.constructor === Derived) console.log(Derived.prototype.__proto__ === Base.prototype) console.log(Base.prototype.__proto__ === Object.prototype) console.log(Derived.__proto__ === Base) console.log(Base.__proto__ === Function.__proto__) console.log(function(){}.__proto__ === Function.prototype) console.log(function(){} instanceof Function)ES5以前
ES5以前は、 Java 風の class 構文はありませんでしたので、次のような書き方をよくしました。
Derived.prototype = new Base()しかし、実はこれでは前述の継承関係を構築するには不十分で、プロトタイプからコンストラクタへの
constructor
フィールドによる参照が構築されません。相互参照すべきところが片参照になっています。
これを正しく実現するには、古いブラウザへの互換性も考慮して以下のような関数を定義する必要がありました。/// Custom inheritance function that prevents the super class's constructor /// from being called on inehritance. /// Also assigns constructor property of the subclass properly. function inherit(subclass,base){ // If the browser or ECMAScript supports Object.create, use it // (but don't forget to redirect constructor pointer to subclass) if(Object.create){ subclass.prototype = Object.create(base.prototype); } else{ var sub = function(){}; sub.prototype = base.prototype; subclass.prototype = new sub; } subclass.prototype.constructor = subclass; }使い方はこんな感じです。
function Base(){ } function Derived(){ Base.call(this) } inherit(Derived, Base)まあ、面倒ですよね。
ES6で class 構文が導入されたのはいいことですが、なんで最初から用意してくれなかったんだと言いたくもなります。これは関数型の継承とプロトタイプベースの継承を好みに応じて選べるようにするためではないかと私は考えています。なぜこうなってしまったのか
コンストラクタとプロトタイプ・オブジェクトが異なるのはなぜなのでしょうか。考えてみればわかりますが、コンストラクタはあくまでも関数なので、プロトタイプに Function を持ち、
call
やapply
といったメソッドを継承しています。新しく定義するクラスは、もちろん関数のお仲間とは限りませんので、プロトタイプチェインに Function を含めたくはないですよね。オブジェクト・モデルを考えればこのような仕様になることは納得できなくもないですが、 C++ や Java のオブジェクト指向に慣れた人には馴染むのが難しいのではないでしょうか。まあ、 Brendan Eich は半意識的にこのようにした1ようですが。
JavaScript 20years という論文でそこらへんの詳細が振り返られています。 JavaScript は10日で最初のバージョンが書かれたという伝説についても、本人が実際のところを明かしている興味深い読み物です。 https://dl.acm.org/doi/10.1145/3386327 ↩
- 投稿日:2020-08-24T21:42:17+09:00
Promise~JavaScript
JavaScroptのPromise処理についてちょっと理解できた気がしたのでアウトプットとして。
では早速
基本的な書き方
function test(value) { return new Promise((resolve, reject) => { setTimeout(() => { if (value) { resolve(value)//成功 } else { reject('値がありません')//失敗 } }, 3000) }) } //実行 test('Hello').then(response => { console.log(response)//結果 setTimeoutの処理の後(約3秒後)に Hello となる }, error => { console.log(`エラー:${error}`) })Promise処理の関数を作りその引数に値を入れたりして
処理が完了(成功)したらresolve()に
今回の場合だと引数に'Hello'と文字列を入れているのでresolve()の方の処理になる。
これを引数なしで実行するとreject()の方の処理になる。
resolve()に引数を入れると、その引数の値を.then()で受け取れる。この様にPromiseを使う事で同期処理の様な形で処理ができる。
.then()の中でreturnして繋ぐこともできる
test('Hello').then(response => { console.log(response)//約3秒後に Hello return test('Bye') }).then(response => { console.log(response)//更に3秒後に Bye return test('Wao!!') }).then(response => { console.log(response)//更に3秒後に Wao!! })結果、上記の処理は全体で約9秒かかる事になる。
複数の処理をまとめて実行 Promise.all
Promise.all([ test('Hello'), test('World'), test('!!') ]).then(response => { console.log(response)//結果 [ 'Hello', 'World', '!!' ] })Promise.allには配列で渡す。
全ての処理が完了したら.then()に値が入る。
この時.then(response)には配列として値が返ってくる普通に書いた場合
function sample1() { setTimeout(() => { console.log('Promise-1') }, 1800) } function sample2() { setTimeout(() => { console.log('Promise-2') }, 1000) } function sample3() { setTimeout(() => { console.log('Promise-3') }, 600) } sample1() sample2() sample3() //結果 Promise-3 Promise-2 Promise-1間に時間のかかる処理等(非同期処理)があったりすると順番通りにいかない
上記をPromiseで書くと
function sample1() { return new Promise((resolve) => { setTimeout(() => { console.log('Promise-1') resolve() }, 1800) }) } function sample2() { return new Promise((resolve) => { setTimeout(() => { console.log('Promise-2') resolve() }, 1000) }) } function sample3() { return new Promise((resolve) => { setTimeout(() => { console.log('Promise-3') resolve() }, 600) }) } sample1() .then(() => sample2()) .then(() => sample3()) //結果 Promise-1 Promise-2 Promise-3と順番通りに処理できる。
間違いがあれば指摘いただけると嬉しいです!
async,awaitもちゃんと覚えなければ、、、
- 投稿日:2020-08-24T21:42:17+09:00
Promise~async-await
JavaScroptのPromise処理についてちょっと理解できた気がしたのでアウトプットとして。
では早速
基本的な書き方
function test(value) { return new Promise((resolve, reject) => { setTimeout(() => { if (value) { resolve(value)//成功 } else { reject('値がありません')//失敗 } }, 3000) }) } //実行 test('Hello').then(response => { console.log(response)//結果 setTimeoutの処理の後(約3秒後)に Hello となる }, error => { console.log(`エラー:${error}`) })Promise処理の関数を作りその引数に値を入れたりして
処理が完了(成功)したらresolve()に
今回の場合だと引数に'Hello'と文字列を入れているのでresolve()の方の処理になる。
これを引数なしで実行するとreject()の方の処理になる。
resolve()に引数を入れると、その引数の値を.then()で受け取れる。この様にPromiseを使う事で同期処理の様な形で処理ができる。
.then()の中でreturnして繋ぐこともできる
test('Hello').then(response => { console.log(response)//約3秒後に Hello return test('Bye') }).then(response => { console.log(response)//更に3秒後に Bye return test('Wao!!') }).then(response => { console.log(response)//更に3秒後に Wao!! })結果、上記の処理は全体で約9秒かかる事になる。
複数の処理をまとめて実行 Promise.all
Promise.all([ test('Hello'), test('World'), test('!!') ]).then(response => { console.log(response)//結果 [ 'Hello', 'World', '!!' ] })Promise.allには配列で渡す。
全ての処理が完了したら.then()に値が入る。
この時.then(response)には配列として値が返ってくる普通に書いた場合
function sample1() { setTimeout(() => { console.log('Promise-1') }, 1800) } function sample2() { setTimeout(() => { console.log('Promise-2') }, 1000) } function sample3() { setTimeout(() => { console.log('Promise-3') }, 600) } sample1() sample2() sample3() //結果 Promise-3 Promise-2 Promise-1間に時間のかかる処理等(非同期処理)があったりすると順番通りにいかない
上記をPromiseで書くと
function sample1() { return new Promise((resolve) => { setTimeout(() => { console.log('Promise-1') resolve() }, 1800) }) } function sample2() { return new Promise((resolve) => { setTimeout(() => { console.log('Promise-2') resolve() }, 1000) }) } function sample3() { return new Promise((resolve) => { setTimeout(() => { console.log('Promise-3') resolve() }, 600) }) } sample1() .then(() => sample2()) .then(() => sample3()) //結果 Promise-1 Promise-2 Promise-3と順番通りに処理できる。
間違いがあれば指摘いただけると嬉しいです!
async,awaitもちゃんと覚えなければ、、、
- 投稿日:2020-08-24T19:42:58+09:00
Phaser3でボタンクラスを作る
あらすじ
Phaser3でUIコンポーネントを別クラスに自作する方法をボタンクラスを例に書いていこうと思います。UIコンポーネントを別クラスに記述できると複数人での作業の分担などが行いやすくなるので、役に立つのではないのでしょうか?
(あと、Atomic Designのようなコンポーネントをファイル等で分ける前提の手法もとれるようになりますね)やり方
プログラム全文
import * as Phaser from "phaser" interface Props{ width?: number; height?: number; onClick?: Function; } export default class Button extends Phaser.GameObjects.Container { seKey: string = ""; text: Phaser.GameObjects.Text = null; container: Phaser.GameObjects.Rectangle = null; constructor (scene: Phaser.Scene, x, y, text, props: Props, { align = 'center', fontSize = 15, color = "black" } = {}) { super(scene, x, y) const { width = 200, height = 40, onClick } = props this.scene = scene; this.scene.add.existing(this); this.setSize(width, height).setInteractive(); const alignLeft = align === 'left'; this.text = scene.add.text(alignLeft ? -width / 2 + 0 : 0, -1, text, { align, fontSize , color}).setOrigin(alignLeft ? 0 : 0.5, 0.5).setPadding(0, 2, 0, 0) this.text.setColor("black"); this.container = scene.add.rectangle(0, 0, width / 2, height / 2); this.container.setStrokeStyle(1, 0xffffff).setOrigin(alignLeft ? 0 : 0.5, 0.5) this.add([this.container, this.text]) this.on('pointerover', () => { this.text.setColor("white"); }) this.on('pointerout', () => { console.log("aaa"); this.text.setColor("black"); }) this.on('pointerup', p => { onClick && onClick(p); }) } setSeKey(key){ this.seKey = key return this } setText (text) { this.text.setText(text) return this } }これは一例です。書き方に多少の癖や慣れ、宗派などはあると思います。私はReactをよく使うので、Reactに記法を寄せたいなと頑張ってみました。プログラムの流れとしては
Phaser.GameObject.Container
を継承したクラスを作成し、そこにいろいろなものを詰め込んでいくという手法を取ればできると思います。これから詳細を解説します。詳細解説
scene受け取り
this.scene = scene; this.scene.add.existing(this); this.setSize(width, height).setInteractive();シーンを受け取り、
this.scene
に詰め込むことでどこでも扱えるようにします。setInteractive
は呼び出さないと後述するthis.on
が使えなくなるので気を付けましょう。パーツ宣言
const alignLeft = align === 'left'; this.text = scene.add.text(alignLeft ? -width / 2 + 0 : 0, -1, text, { align, fontSize , color}).setOrigin(alignLeft ? 0 : 0.5, 0.5).setPadding(0, 2, 0, 0) this.text.setColor("black"); this.container = scene.add.rectangle(0, 0, width / 2, height / 2); this.container.setStrokeStyle(1, 0xffffff).setOrigin(alignLeft ? 0 : 0.5, 0.5) this.add([this.container, this.text])ボタンに表示するテキストとボーダーラインを作成し、画面と自身に追加します。ここで色や線の太さなどを設定しています。
setOrigin
を0.5に設定することで中央ぞろえにすることが出来ます。hover等の作成
this.on('pointerover', () => { this.text.setColor("white"); }) this.on('pointerout', () => { console.log("aaa"); this.text.setColor("black"); }) this.on('pointerup', p => { onClick && onClick(p); })「マウスを乗せたときに色が変わる」などの小技はここで行います。一番下の
pointerup
はクリック時の処理を行っています。ボタンクラス呼出し
this.button = new Button(scene, x, y, text, { onClick: () =>{ console.log("Click!!"); } })ボタンクラスを
Phaser.Scene
クラス内で呼び出す場合、scene
はthis
になります。あとはx座標, y座標, ボタンに表示したいテキスト、その他もろもろ(今回はクリックされたときのイベント)を引数として渡します。実行結果
背景色が #cccccc の所にボタンを置いてみました!
まとめ
文字色、ボーダーなどをいろいろ触ればより良いボタンが作れると思います。これを応用すればボタン以外のUIコンポーネントも作成可能です。よりよくするアイデアがある方はコメントをお願いします!
Phaser3で遊ぶ用のリポジトリに実例を挙げているので、分かりにくかった方はこちらもご参照ください!
phaser-game : Github参考
Web屋がJavaScriptでゲームを作ってSteamで配信するまでの道のり
https://qiita.com/laineus/items/0bb62f58910ccdfa1d34
https://github.com/laineus/unsung-kingdom
- 投稿日:2020-08-24T19:42:20+09:00
esbuildがwebpackより187倍早いらしいので環境構築しよう
はじめに
久しぶりの投稿になります。
今回は以下の記事で、esbuidがすごい!!という話を聞きつけこの記事を書くことにしました。
参考: [Web フロントエンド] esbuild が爆速すぎて webpack / Rollup にはもう戻れないどのくらいすごいのでしょうか?
参考に挙げている記事によるとesbuild は Go 言語で書かれた JavaScript および TypeScript のビルドツールです。 esbuild 単体でトランスパイル + バンドル + ミニファイできます。 JSX / TSX もサポートされています。そしてめっちゃくちゃ速いという触れ込みです。最初から速度を意識して無駄がないように書かれており、構文解析・出力・ソースマップ生成は並列化され、ネイティブコードで動作します。公式の README では three.js のビルドが Rollup + terser より 100 倍速い と謳っています。
https://www.kabuku.co.jp/developers/ultrafast-tsx-build-tool-esbuildとのことです。
なるほどなるほどと。最近わたしもWebpack
のBuild遅いなあと思っていたのでこの情報を鵜呑みにして、esbuild
の環境構築をしたくなりました。また、ドキュメントによるとesbuildはwebpackの187倍もBuildが早いみたいです。
esbuildの環境構築
環境構築についてですが、だいたいのモジュールバンドラにはコンフィグファイルがつきものです。esbuildのgithubを参考にしましたが、ワンライナーのCL上での実行例のみでコンフィグファイルらしいものの書き方見当たりません。
少し詳細に調べたところ、
https://github.com/evanw/esbuild/issues/39
以下に書いてありました。
ふむふむ、一応、対応はしているみたいです。前準備
今回は、
React+TypeScript
で記述されたプロジェクトを対象にBuildを行う環境構築をします。Vueについては、vite
というモジュールバンドラがesbuild
を利用して、Buildを行っているため、そちらを利用してくださいとのことです。まずは以下の通りにnpmモジュールをインストールします。
npm install -D esbuild @types/node
Reactのプロジェクトは後ほど説明する注意点に気をつければなんでも大丈夫です。
わたしのgithubのプロジェクトを例に説明します。
https://github.com/olt556/esbuild-tmpBuildスクリプトの作成
まず最低限、以下の条件を実現したいです。
- developmentとproductionの環境でBuildを分ける
- エントリーポイントの指定
- Build後に出力されるESの規格の指定
- プラットフォームの指定(node, browser)
- production時にはminifyをかける
- 出力先ディレクトリの指定
- tsconfig.jsonの読み込み
以上の条件を元に作成した、esbuildのBuildファイルは以下の通りです。
build.tsconst { argv } = require('process') const { build } = require('esbuild') const path = require('path') // optionsの定義 const options = { // 以下のdefineプロパティを設定しない場合Reactのプロジェクトの実行時にエラーが出ます define: { 'process.env.NODE_ENV': process.env.NODE_ENV }, entryPoints: [path.resolve(__dirname, 'src/Index.tsx')], minify: argv[2] === 'production', bundle: true, target: 'es2016', platform: 'browser', outdir: path.resolve(__dirname, 'dist'), tsconfig: path.resolve(__dirname, 'tsconfig.json') } // Buildの実行 build(options).catch(err => { process.stderr.write(err.stderr) process.exit(1) })上記のファイルを以下のnodeコマンドで実行することでBuild可能です。
node build.ts production/development
npmスクリプトにして実行しやすくするのもいいかもしれませんね。
実装時の注意点(2020/08/24)
現時点でesbuildを利用する際には、以下のような注意点があります。
TypeScript
のトランスパイル時の型チェックに対応していませんcss-modules
に対応していませんplugins
に対応していないので、各種loaderの読み込みや詳細な設定をすることはできませんおわりに
webpack
をesbuild
に置き換えることによって、Reactを導入しているプロジェクトがCSSフレームワーク
や、CSS in JS
を利用している場合、かなりの効果を発揮するかもしれませんね!!もし何か質問やご指摘などありましたらお願いします!
- 投稿日:2020-08-24T19:37:03+09:00
confirm内で改行する
改行したい箇所に
\n
を書くだけですexample.jsvar result = confirm('text\ntext');
- 投稿日:2020-08-24T19:31:45+09:00
【ES6】配列メソッド~findIndex編~【JavaScript】
- 投稿日:2020-08-24T18:43:05+09:00
JavaScriptのpostMessageでDOMツリーのノード参照を渡す方法[Xpath]
ポップアップしたウィンドウに要素の参照(DOMノード)を送りたかったので、この記事を書いた。
Web MessagingはDOMノードを送れない
JavaScriptでは、window.postMessageを使うことで、ポップアップやiframeなどの別ウィンドウとWeb Messaging(HTML5)を介して通信することができる。
子ウィンドウ、親ウィンドウへの参照はそれぞれ
window.open()
とwindow.opener
で持つことができるから、window.postMessage
と併せてあらゆるデータのやり取りが自由にできそうなものである。しかし、
window.postMessage
では送ることができないデータがある。MDN web docsには、
message
について他のウィンドウに送られるデータ。データは the structured clone algorithm に従ってシリアル化されます。つまり、手動でシリアル化することなく様々なデータオブジェクトを渡すことができます。
(window.postMessage - Web API | MDN)と書かれている。
この「the structured clone algorithm」(日本語「構造化複製アルゴリズム」)という部分が重要で、この中で「構造化複製で動作しないもの」というのが示されいる。
- Function オブジェクトは構造化複製アルゴリズムでは複製されません。複製しようとすると DATA_CLONE_ERR 例外が送出されます。
- DOM ノードを複製しようとしても同様に DATA_CLONE_ERR 例外が送出されます。
つまり、例えば
document.querySelector()
などを使えば要素の参照を取得できるが、このような参照はWeb Messagingで送ることができない。
実際にwindow.postMessage
でDOMノードを送ろうとすると、Uncaught DOMException: Failed to execute 'postMessage' on 'Window': HTMLButtonElement object could not be cloned.
のようなエラーが発生する。
ウィンドウ間で互いのDOMの参照はできるのだから、DOMノードも送れるべきである。
そこで、住所のように、テキストでDOMツリーにおけるノードの位置を表現できる方法を探していると、「Xpath」という言語構文を見つけた。Xpathとは
Xpathとは、XMLやHTMLのようなツリー状の階層構造を持つ文書で、様々なノードの位置や情報を表すことができる記法のことである。URLのようなパス表記ができることが特徴。
Introduction to using XPath in JavaScript | MDN
記法についてはこちらの記事が詳しいが、ざっくり言うと、例えばbody直下の
<h1>
にアクセスするためのXpathは/html/body/h1となる。
また、2番目の
<div>
の3番目の<span>
にアクセスするためのXpathは/html/body/div[2]/span[3]といったように表すことができる。
要素のXpathを取得して送信する
まず、送るためにはDOMノードのXpathを取得する必要がある。
以下の記事のコードを使用して送信側のスクリプトを書いた。ブラウザ上のクリックした要素のXpathを取得する - Qiita
parent.htmlのjslet childWindow = window.open('child.html', 'child', 'width=300,height=400,scrollbars'); // クリックされたらその要素のXpathを子ウィンドウにpostMessageする window.addEventListener('click', (e) => { childWindow.postMessage(getXpath(e.target), 'http://localhost'); }); /* https://qiita.com/narikei/items/fb62b543ca386fcee211 */ function getXpath(element) { if(element && element.parentNode) { var xpath = getXpath(element.parentNode) + '/' + element.tagName; var s = []; for(var i = 0; i < element.parentNode.childNodes.length; i++) { var e = element.parentNode.childNodes[i]; if(e.tagName == element.tagName) { s.push(e); } } if(1 < s.length) { for(var i = 0; i < s.length; i++) { if(s[i] === element) { xpath += '[' + (i+1) + ']'; break; } } } return xpath.toLowerCase(); } else { return ''; } }要素のXpathを受け取って参照する
受信側は以下のようになる。
child.htmlのjslet parent = window.opener.document; function receiveMessage(e) { if (e.origin !== "http://localhost") {return} parent.evaluate(e.data, parent, null, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue.innerHTML = 'ok!'; } window.addEventListener("message", receiveMessage);Introduction to using XPath in JavaScript | MDN
成功すると、親ウィンドウでクリックした要素に「ok!」と表示されるはず。
これで、親ウィンドウから子ウィンドウに送られたXpathを使って、子ウィンドウが親ウィンドウのDOMを参照し、当該要素にアクセスすることが可能になった。もちろんその逆も可能である。
- 投稿日:2020-08-24T17:43:22+09:00
webサービス(SPA)の読み込み速度を改善する備忘録(JS編)
あらすじ
とあるプロジェクトが忙しい峠を越えて運営が少し落ち着いてきたころに、サービスの読み込み速度が遅いな~~~って思っていたことが多々ありました。
CSS、その他編も別で書いていきたいと思うけど、まずはJS編ということでwebサービスの読み込み改善を行ったときの備忘録書きます。
この時は開発環境での実験になるんで、本番にも反映したいなーと思っている次第です。※備忘録なのでかなり読みにくいと思います。ごめんなさい。
環境
webpack 4.29.6 webpack-cli 3.3.0 react 16.8.6目標
jsファイルの読み込みで、読み込み速度の遅いファイルは圧倒的に bundle.js の読み込みが遅いため、ファイルを軽くしたい
(現状はentryしているものをビルドするとbundle.jsを自動生成しているだけ)また、他にもcdnから読み込んでいるjsファイルだったり、他にも読み込み速度を阻害しているファイルがあるかチェック⇒あれば対応
※いずれも開発環境で実験
※ある程度webpackが理解できた前提で記載します現状はどうなっているか
開発環境での検証のため、minifyなし、logも出てます。
スーパーリロードしてbundle.jsのみ確認してみますとbundle.js size 12.6MB time2.73s3秒かかるんですね。。調子悪いともっとかかります。
全ての読み込みが終了するまで約20秒。ユーザーからするとかなり不快。
通常リロードでも16秒。
cdnで読み込んでいるjsファイルはそこまで重くなかったですが、一応チェックします。解決策の模索
いらないモジュールは削除
特にいうことは無いですがbundle.jsで使用してないモジュールは削除。
開発当初使う予定だった、使っていたが使わなくなったものは削除していきました。
言わずもがなですが一応。
(このときはjqueryやTwitter関連のjsファイルやモジュール)scriptタグにdeferを追加
bundle.jsとは分離しているjsファイルの読み込みはindex.htmlにて読み込んでます。
このscriptタグで呼ばれているjsファイルにdeferを追記します。
<script src="https://cdnjs.cloudflare.com/ajax/libs/test/test.js" defer></script>
scriptタグにasyncやdeferを付けない場合、
HTMLパースを不必要に止めてしまうためデフォルトではない属性(HTMLパースを阻害しない属性)を追加する必要がありました。
今回の場合、主にbundle.jsが原因で読み込みが遅くなっていることはあるものの、少しでも読み込みを高速化するべく対応します。今回deferで対応したのはasyncの場合、読み込み実行順序が不定のためです。(deferは順番に読む)
詳しくは参考文献に記載しておきます。webpackのsplitChunksを使う
以前CommonsChunkPluginだったものです。CommonsChunkPluginがwebpack4で廃止され、splitChunksが登場したということですね。
splitChunksはoptimizationプロパティにつけれられるもので、
複数のエントリーポイントで共有のモジュールを使っている場合、その共通モジュールだけ分離して(chunkに分離)設定するものです。
また、共通ファイルは共通なのでキャッシュからも読み込んでくれます。このプロジェクトではReactを使っていますが、いたるところで呼び出していたjsファイルでReactがバンドルされていた、つまり重複してたところを共通化してくれるというわけですね。
webpackにはこのように記載しました。
/*一部省略*/ // モジュールの分割 const SPLIT_CHUNKS = { // 分離されて生成される chunk の名前(任意の名前) name: 'vendor', // 対象とするチャンク(chunk)に含めるモジュールの種類 chunks: 'initial', // または 'all' } module.exports = { // 環境によってentryを変える entry: IS_DEVSERVER ? [ 'react-hot-loader/patch', `webpack-dev-server/client?http://localhost:${ PORT }`, 'webpack/hot/only-dev-server', `${ SRC }/index.js`, ] : { 'bundle': `${ SRC }/index.js`, }, output: { path: DIST, publicPath: '/', filename: IS_DEVSERVER ? `bundle.js` : `[name].js`, }, resolve: { alias: ALIAS, extensions: ['.js'], }, stats: { colors: true }, module: { rules: [ { test: /\.js$/, exclude: [/node_modules/], use: { loader: 'babel-loader' } }, { // test: /\.scss$/, test: /\.(sa|sc|c)ss$/, loader: !IS_DEVSERVER ? [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { url: false } }, 'postcss-loader', 'sass-loader' ] : [ MiniCssExtractPlugin.loader, 'css-loader?url=false', 'postcss-loader?sourceMap=inline', 'sass-loader' ] }, ] }, plugins, devServer: { contentBase: DIST, historyApiFallback: true, host: 'localhost', port: PORT, inline: true, hot: true, disableHostCheck: true, }, }, optimization: { minimizer: MINIMIZER, splitChunks: SPLIT_CHUNKS // 追記 }, performance: { hints: false } }これでvendor.jsとして共通モジュールを分割してくれます。
SPLIT_CHUNKSに記載しているchunksには3種類あり、initial⇒初期リロードに必要な場合 all⇒testに含まれる(ここでいうnode_modules)全てを分離 async⇒import()を使ってダイナミックに使用する場合に分離初期リロードだけでいいので今回はinitialを指定しています。
※allも試してみましたが、ビルドにめちゃくちゃ時間かかったのでやめました。allでもやりようによってはいいやり方があるかもしれませんが、今回はinitial。
オプションもたくさんあります。詳しくは公式をご覧ください。結果
最初はスーパーリロードして読み込みます。
bundle.js size 4.1MB time 826ms vendor.js size 8.5MB time 1.51sbundle.jsもだいぶ読み込みがはやくなりました。
スーパーリロードで10秒
通常リロードで6秒で全読み込み環境してます。
(6秒でも遅いので、今度はcss、その他編でもっと軽くできると思う。カスタムフォント削るとか。)
cdnのほうはあんまり変わらなかったかも。。
bundle.jsがかなり読み込みの阻害をしていた感じでした。これは本番環境でも取り入れていきたいです。
参考文献
- 投稿日:2020-08-24T17:27:02+09:00
【Rails】Amazon PA API v5.0 で書籍検索(非同期通信)
こちらの記事の続きです。
前回はAPI連携後にviewファイルを表示させていましたが、今回はajax通信でデータを取得して書籍検索結果を表示します。
(理由はこの方がかっこいいからです笑)完成イメージ
(cssで見た目を少し整えたので、前回の記事と少し見た目は変わってます)
処理の流れ
検索フォームに入力して検索ボタンを押す(submitする)と以下のように処理されます
1. 検索フォームのキーワードを取得
2. json形式でデータ(キーワード)を持たせて、指定のURLにリクエスト(ajax通信)
3. コントローラでAPI連携の処理をし、jsonでデータを渡す
4. 返ってきたデータを使ってhtmlを書き換えて検索結果を表示各ファイル
コントローラ
コントローラにjsonでレスポンスを返すという記述が必要です。
respond_to 〜
のところsearches_controllerclass SearchesController < ApplicationController before_action :call_client, only: :index def index si = @client.search_items(keywords: keyword_params, SearchIndex: "Books") @items = si.items respond_to do |format| format.html format.json end end private def call_client require 'paapi' @client = Paapi::Client.new(access_key: Rails.application.credentials[:pa_api][:access_key_id], secret_key: Rails.application.credentials[:pa_api][:secret_key], market: :jp, partner_tag: Rails.application.credentials[:pa_api][:associate_tag]) end def keyword_params params.require(:keyword) end endjbuilder
views/[コントローラ名と同じ名前のディレクトリ]
の中にindex.json.jbuilder
を作成します。
今回はcontrollers
直下にsearches_controller.rb
を作っているので、views/searches/index.json.jbuilder
となります。今回jsonデータは配列になっているので、各要素をどのように加工するか書く必要があります。取り出すデータは画像、タイトル、著者、出版社の情報にしています。
index.json.jbuilderjson.array! @items do |item| json.image_url item.image_url json.title item.title json.authors item.authors json.publisher item.publisher endjsファイル
api-search.js// jQueryを使って記述します $(function() { // 検索結果を表示する関数 let search_list = $("#books") function appendBook(image_url, title, author, publisher) { const html = `<div class="search-book-content"> <div class="book-image"> <img class="book-image" src="${image_url}"> </div> <div class="right-content"> <div class="book-info"> <div class="book-info__title"> ${title} </div> <div class="book-info__author"> ${author} </div> <div class="book-info__publisher"> ${publisher} </div> </div> </div> </div>` search_list.append(html); } // 検索中の表示関数 function dispLoading(msg){ let dispMsg = "<div class='loadingMsg'>" + msg + "</div>"; $("body").append("<div id='loading'>" + dispMsg + "</div>"); } // 検索中の表示を消す関数 function removeLoading(){ $("#loading").remove(); } // 検索ボタンを押した時にイベント発火 $("#book-search-form").on("submit", function(e) { e.preventDefault(); const keyword = $("#keyword").val(); dispLoading("検索中..."); // ajax通信の詳細 $.ajax({ url: '/searches', type: 'GET', data: {'keywords': keyword}, dataType: 'json', timeout: 10000 }) // ajaxがうまくいったとき .done(function(items){ $(".search-book-content").remove(); items.forEach(function(item){ let image_url; let author; let publisher; if (item.image_url == null) { image_url = `/assets/no_image-267acfcb976ba4942183409c682b62a768afb48c328b6ba60de7b57fd83c3b56.png` } else { image_url = item.image_url } if (item.authors.length == 0) { author = '不明な作者' } else { author = `${item.authors[0]}` } if (item.publisher == null) { publisher = '不明な出版社' } else { publisher = item.publisher } let title = item.title appendBook(image_url, title, author, publisher); }) }) // ajaxが失敗した時 .fail(function(){ alert("検索に失敗しました"); }) // ajaxが成功しても失敗しても共通の処理 .always( function(data) { // Lading 画像を消す removeLoading(); }); }); })コードを読めばわかると思いますが、処理の流れを書いてみます。
■ 関数の定義
- appendBook
本のデータを元にhtml作成する関数- dispLoading
検索中のgifファイルを表示させる関数- removeLoading
検索中のgifファイルを非表示にする関数■ イベント
- submitアクションでイベント発火
- フォームに入力されたキーワードを取得
- 検索中の画面を表示
- ajax通信のリクエスト(urlや送信データを指定)
- ajax通信が成功したときの処理
各データを変数にして、appendBook関数に食わせる (データが取得できない場合は不明な作者などのデータが入るようにしておく)- ajax通信が失敗したときの処理
- 共通処理として検索中の画像を消す
検索に少し時間がかかるので、検索中であることがわかりやすいように、ajax通信中はgif画像を表示させるようにしています。
また、検索に時間がかかりすぎた場合にエラーになるようにしています。(10秒)
ずっと検索中になっているとユーザー側が不安になるので。検索中の画面(cssファイル)
cssファイル#loading { display: table; width: 100%; height: 100%; position: fixed; top: 0; left: 0; background-color: #fff; opacity: 0.8; } #loading .loadingMsg { display: table-cell; text-align: center; vertical-align: middle; padding-top: 140px; background: image-url("loading.gif") center center no-repeat; }gifファイルは無料で作れるサイトがいくつかあるので作ってみましょう。
僕はこのサイトでつくりました。メモ
うまくいかなかった部分をメモに残しておきます。
(詳しい方は教えていただけると嬉しいです…)
本当はkeyword_paramsだけを引数にしたい
コントローラでkeywordを元にAPIを叩く際に、search_itemsの引数にはkeyword_params
としてハッシュ形式で渡せばスッキリするのですが、なぜかうまくいきませんでした。(activehashだと受け取ってくれない感じ…)turbolinksを消すとうまく作動しない
turbolinksが入っているとajax通信する際にうまくいかないことがあるらしく、いつも使わない設定にしていたのですが、逆にturbolinksがないせいでajax通信がうまく行かないという事態になりました笑。この辺の仕組みはいまいち理解できてません…
- 投稿日:2020-08-24T17:21:42+09:00
同期非同期コールバック
新人の頃訳がわからなかった三兄弟を JavaScript を使って説明してみる
同期処理
記述された通りの順番に処理すること
console.log('前の処理が終わるまで') console.log('次の処理は') console.log('行われません')実行結果
非同期処理
他の処理と並列で処理を行うこと
setTimeout(() => { console.log('どうぞどうぞ') }, 100) setTimeout(() => { console.log('いやおれが') }, 50) console.log('ここはおれが')実行結果
プログラムの記述された順番通りに実行されません(行番号の順番通りという意味で)コールバック
一番わけがわからなかったやつ。こいつを説明したいがために記事を書いた。
〇〇したら(されたら)引数として渡された関数を実行するというのが、コールバックの考え方だ
例えば
addEventListener
というメソッドがある
文字通り、イベントのリスナーを(HTMLElementに)追加するメソッドだ
第一引数にイベント名、第二引数にコールバック関数(イベントリスナー)を追加する
使い方はこんな感じwindow.addEventListener('click', () => { console.log('window がクリックされました') })これにより、windowをクリックすると
window がクリックされました
とコンソールログが出るようになる。コンソールログを出す関数をコールバック関数として引数に渡しているからだ。当然、
window.addEventListener('click', function() { 複雑な処理 })書き方が変わろうが、実行する処理が複雑になろうが、windowがクリックされたら第二引数の関数が実行されるところは変わらない。
また、先ほど例に出したsetTimeout関数も、引数にコールバック関数を取っている
// setTimeout(コールバック関数, 何秒待つかms)これは第二引数に渡されたミリsec分待ってから、第一引数に渡された関数を実行するという関数
addEventListenerコールバックの例と比べて、ウィンドウでイベントが発火したら、という条件から何ms待ってからという条件に変わったものの、コールバックの考え方自体は変わっていない。〇〇したら(されたら)引数として渡された関数を実行している
コールバックは他の言語でも実装されている。JavaScriptだけではなく、Javaにだって、Objective-cにだって、Swiftにだって存在している。
スマホの画面をつけた時、ページ遷移した時、ボタンが押された時など、とにかく〇〇したら(されたら)引数として渡された関数を実行する = コールバック は色々なところで使われているが、考え方は変わらない
〇〇したら(されたら)引数として渡された関数を実行するという考え方だもしよくわからない場合は、わからないメソッド(関数)のリファレンスをちゃんと読んで、使用例を見て、正しいメソッドの使い方を調べればきっとできるはずだ
- 投稿日:2020-08-24T17:18:32+09:00
Next.jsのプリレンダリング方式についてまとめてみた
最近Next.jsを使い始めましたが、Next.jsにおいて重要な概念であるプリレンダリング方式(SSR・SSG)についてあまり理解できなかったため、公式ドキュメントを参考に、それぞれの方式の違いや使い分けについてまとめました。
この記事では、用語の説明にフォーカスしており、実装方法は記載してませんのでご了承ください。
プリレンダリングとは?
プリレンダリングとは、簡単にいうと事前にHTMLを生成することです。
通常のReactアプリケーション(SPA)の場合、ユーザーがWebページにアクセスし、Webページを表示する時にブラウザ側でHTMLを生成します。(クライアントサーバーレンダリング)
プリレンダリングでは、ユーザーがアクセスする前に事前にHTMLを生成し、その用意されたHTMLをユーザーに提供する方式となっています。そのため、ブラウザの負荷を下げて表示を高速化することができます。
また、事前にHTMLが生成されているため、検索エンジンのクローラーに全てのコンテンツを見せることができます。
SPAのSEO的なデメリットをカバーできることもプリレンダリングの強みの一つです。Next.jsでは、デフォルトで全てのページでプリレンダリングが有効化されています。
プリレンダリング方式(SSR・SSG)の違い
Next.jsでは、2種類のプリレンダリング方式(SSR・SSG)があり、それぞれページごとに自由に選択して実装することができます。
この2つの方式の違いは、主にHTMLを生成するタイミングになります。SSR(Server Side Rendering)
SSRでは、ユーザーがアクセスした時にサーバー側でHTMLを生成します。
SPAとの違いについてを簡単に説明しますと、
SPAではブラウザ側でHTMLを生成していましたが、SSRではサーバー側でHTMLを生成し、レンダリング済みのHTMLをブラウザ側に提供します。
要するに、ブラウザの大半の仕事をサーバー側に任せ、ブラウザの仕事は最後の描画だけとなります。SSRは、リクエストごとにHTMLを生成するため、常に最新の状態をユーザーに見せることができます。
SSG(Static Site Generator)
SSGでは、アプリビルド時にHTMLを生成します。
リクエストごとにHTMLを生成せず、事前にビルドされたHTMLを再利用する形となるため、SSRよりもさらに高速な表示が可能です。
プリレンダリング方式(SSR・SSG)の使い分け
公式ドキュメントでは、以下の通り、基本的にはSSGを使用することが推薦されています。
We recommend using Static Generation (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.
意訳:可能な限りSSG(データの有無にかかわらず)を使用することをお勧めします。なぜなら、あなたのページは一度構築され、CDNによって提供されるので、サーバーがリクエストごとにページをレンダリングするよりもはるかに速くなります。
ただし、SSGはビルド時にHTMLを生成するため、更新頻度の高いページには適していません。
SNSや動画配信サイトといったリアルタイムにWebサイトの表示を変えたいページに関しては、SSRを選択するのが最適かと思います。
参考資料
- 投稿日:2020-08-24T17:13:12+09:00
WEBフロントエンドパフォーマンス要約 基礎編 2020年
WEBフロントエンドパフォーマンス前編 2020年
本職ではバックエンドエンジニアとして仕事をしているのですが、最近ではWEBフロントエンドの仕事も増えてきました。
ただ、WEBフロントエンドパフォーマンスについてしガッツリ勉強したことがなかったので、Webフロントエンド ハイパフォーマンス チューニング書籍で学びました。
内容を忘れない為にも、こちらに備忘録を残します。
基礎編と発展編に分けた2部構成にします。
この記事を読むだけで、ある程度はWEBフロントエンドパフォーマンスについての基礎レベルは理解できます。
より詳しい内容を知りたい方は書籍の購入をオススメします。
■ 対象読書
- WEBフロントエンドエンジニア - WEBフロントエンドパフォーマンスについてよく知らない - ネットワーク基礎知識について取得済みWEBパフォーマンスとは何か?
パフォーマンスを定義する
WEBパフォーマンスの定義とは以下のように定義します。
ユーザーの様々な振る舞いに対してWebページが応答する速さWEBパフォーマンスを改善することで、ユーザーの目的達成のために費やす時間やリソースを節約することが可能になります。
パフォーマンスの重要性
昨今ではインターネットの回線速度の改善やPCスペックが向上しましたが、モバイル環境や低スペックのマシンではパフォーマンスが落ちるという問題点はいまだに存在します。
よって、WEBパフォーマンスのチューニング作業は現在でも重要な作業と言えるでしょう。
また、WEBパフォーマンスを向上させることにより、ユーザー満足度の向上も期待できます。
新たに重要になった描画パフォーマンス
JavaScriptの進化やAjaxの進化により、現在のウェブアプリケーションは複雑なインタラクションを持つアプリが増えてきました。
したがって、ウェブページ内でのインタラクション(描画パフォーマンス)の最適化にも気をつけるべきです。
ハイブリットアプリの存在
Apache Cordova(Android, Ios)やElectron(Windows,Mac)等のハイブリット開発フレーム枠の存在も描画パフォーマンスを考えるときには見逃せません。
ハイブリッドアプリでは、初期ロードのパフォーマンスが比較的問題になりやすいので描画パフォーマンスを意識する必要があります。
パフォーマンスチューニングの第一歩
WEBアプリケーションのパフォーマンスチューニングを理解する上では、ブラウザの仕組みをまず理解することが大事です。
仕組みの理解が曖昧のままパフォーマンスチューニングしても、なぜそこが問題になっていうるのか、解消する為にどうすれば良いのかが、あやふやになってしまいます。
ブラウザのレンダリングの仕組み
ブラウザのレンダリングの仕組みを知ることは、パフォーマンスチューニングの重要な基礎です。
この基礎があるかどうかでウェブパフォーマンスの問題に立ち向かう際の効率が大きく変わります。
ブラウザののコンポーネント
ブラウザは通常いくつかのソフトウェアコンポーネントによって構成されています。
チューニングをする為に知っておきたいコンポーネントは以下の2つです。
1. レンダリングエンジン 2. JavaScriptエンジン■ レンダリングエンジン
レンダリングエンジンとはブラウザの内部で利用されるHTMLの描画エンジンを指します。レンダリングエンジンは、HTMLや画像ファイル やCSSやJavaScriptなどの各種リソースを読み取って、それを画⾯上の実際のピクセルとして描画します。
描画のみを担当しているので、ユーザーが普段 利⽤しているWEBブラウザのユーザーインターフェイス※1を持っていません。
※1ブックマーク機能や戻るボタン、進むボタンなどのこと■ JavaScriptエンジン
JavaScriptエンジンは、JavaScriptの実⾏環境を提供するソフトウェアコンポーネントです。レンダリングエンジンとともに、ブラウザ内部で利⽤されます。
各ブラウザのレンダリングエンジンとJavaScriptエンジンまとめ
ブラウザのレンダリングの流れ
例)ブラウザのアドレスバーにURLを打ち 込んで、ウェブページが表⽰されるまで
リソースの読み込み -Loading
まず最初に行われるのがリソースの読み込み処理です。
リソースのダウンロードでウェブページからリソース※1をサーバーからダウンロードします。
次に、受け取ったリソースをパース(構文解析)してレンダリングエンジンの内部表現に変換します。※2
※1 リソースとはHTML,CSS、JavaScriptファイル、JPEG,PNGなど※2 パースとはHTMLの場合はDOMツリーに、CSS(CSS Object Model)の場合はCSSOMツリーに変換することJavaScriptの読み込み
リソース一式を読み込んだ後、JavaScriptのコードをJavaScriptエンジンに引き渡して実行させます。
JavaScriptのコード解析は以下のような工程で実施されます。
■ 字句解析と構文解析
与えられたコードを何らかの実行可能な形式に変換、コンパイした上でJavaScriptを実行します。1. JavaScriptのソースコードを文字解析を通じて、トークンに変換 2. 変換されたトークンの列を構文解析にかけて抽象構文木に変換。■ コンパイル
構文解析されたものを、コンパイラがコードをその処置系(chromeならv8)が動作しているマシーンのCPUが直接解釈できる機械語に変換します。■ 実行
最終的に実⾏可能な形式にコンパイルされた JavaScriptのコードは、処理系内部の仮想マシン、もしくはCPUで実⾏されます。レイアウトツリー構築
JavaScriptの実行が終わると、今度はレイアウトツリー構築に移行します。
■ Calculate Style
Styleでは、ドキュメントのDOMツリー内の すべてのDOM要素に対して、どのようなCSSプロパティが当たるのかを計算します。■ Layout
DOMツリー内のすべてのノードの視覚的なレイアウト情報の計算、レイアウト(Layout)※1を⾏います。※1 レイアウト情報とは、要素の大きさ、要素のマージン、要素のパディング、要素の位置、要素のZ軸の位置になります。レンダリング結果の描画
DOMツリーのレイアウト情報の算出が終わると、 最後のレンダリング結果の描画(Painting)に移ります。
■ Paint
Paintでは、内部の低レベルな2Dグラフィックエンジン向けの命令を⽣成します。■ Resterize
Rasterizeで、⽣成された命令を⽤いて実際にピクセ ル(ビットマップ)へと描画します。■ Composite Layers
Layersの処理は、ピクセルにしたレイヤーを合成して最終的なレンダリング結果を⽣成します。チューニングの基礎
ブラウザレンダリングの仕組みを理解したので、次にWEBパフォーマンスチューニングの基本的な考え方とチューニングを行うのに必要なパフォーマンスの計測方法について解説します。(やっと本題に入れる...)
チューニングのトレードオフ
チューニングのトレードオフとして主に2つあります。
1. 開発の時間的コスト 2. コードの単純さ(可読性、保守性、拡張性)※1※1 SPAなどウェブアプリケーションで JavaScriptのコードが⼤きくなると単純さが失われます。その結果、ウェブアプリケーションの保守性や 拡張性が損なわれることになります。無駄なチューニングをすることで、より複雑性が増す可能性があるので、知識のないままチューニングするのはリスクが高いです。(なので、チューニングの前にレンダリングの説明をしました)
推測するな、計測せよ
パフォーマンスチューニンで重要なのはまず計測することです。
多分パフォーマンスが向上しただろうと終わらずに必ず計測して見える化することが大事です。
計測するにはまずボトルネック(パフォーマンスを落としている問題箇所)を探し出します。
次に目指すべきパフォーマンス指標を設定します。
パフォーマンス指標としてRAILという指標があります。
RAILは、Response、Animation、Idle、Loadの各単語の頭文字をとって名付けられました。
Response: 100ミリ秒 Animation: 16ミリ秒 Idle: 50ミリ秒 Load: 100ミリ秒RAILSではこのように4つの項目に対して、それぞれ守るべき基準時間を設けています。
この数値を超えると、パフォーマンス改善が必要になってきます。Response: ウェブページがユーザーインターフェイス上の変化を引き起こして、応答するまでの時間 Animation: アニメーション中に連続して⾏われる フレームの中で1フレームの処理の時間の⽬安を指します Idle: アイドル状態に実⾏されるJavaScriptの処理時間を指します。※1 Load: ウェブページのコンテンツの読み込みにかかる時間を指します。※1アイドル状態とは、⼀度Responseや AnimationやLoadが終了してから何らかのユーザーのアクションを待っている状態を指します。計測する手段
1. Chrome DevToolsなどデベロッパーツールによる計測 2. JavaScriptによる計測 3. パフォーマンス診断ツールの利⽤ 4. パフォーマンスの継続的監視1. Chrome DevToolsなどデベロッパーツールによる計測
Chrome DevToolsの主要な機能の以下3つについて説明します。
Network Performance MemoryNetwork
Networkパネルでは、ネットワークを通じたリソース取得のタイムラインの様⼦を細かくみること可能です。
Networkパネルの利用方法については下記のサイトを参照ください。
■ 公式サイト
https://developers.google.com/web/tools/chrome-devtools/network/resource-loading?hl=jaPerformance
Performanceパネルではページの読み込み、操作に 関するすべてのイベントを分析できます。
Performanceパネルの利用方法については下記のサイトを参照ください。
■ 公式サイト
https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/timeline-tool?hl=jaMemory
Memoryパネルではメモリの利⽤状況を、計測し ます。主にメモリリークの検知やメモリの使い過ぎ を把握するために活⽤します。
2. JavaScriptによる計測
ユーザーの実際のパフォーマンスを確認したい場合はJavaScript中にパフォーマンス計測用のコードを入れておく方法があります。
1. Navigation Timing APIによる計測 2. User Timing APIによる計測 3. Resource Timing APIによる計測1. Navigation Timing APIによる計測
Navigation Timing APIでは、ブラウザのナビゲーション時の詳細なパフォーマンス情報を取得できます。
Navigation Timing APIではwindow.performance.timingオブジェクトとwindow.performance.navigationオブジェクトを利⽤します。
window.performance.timingオブジェクトには、各段階の処理時間のタイムスタンプが得られます。
使用例
var timing = performance.timing; // リダイレクトにかかる時間 console.log(timing.redirectEnd - timing.redirectStart); // ドメインの解決にかかる時間 console.log(timing.domainLookupEnd - timing.domainLookupStart); // 接続の確⽴にかかる時間 console.log(timing.connectEnd - timing.connectStart); // HTTPリクエストの送信からHTTPレスポンスの受信までかかる時間 console.log(timing.responseEnd - timing.requestStart); // ドキュメントの解析時間 console.log(timing.domInteractive - timing.domLoading);2. User Timing APIによる計測
User Timing APIを⽤いると、開発者が任意の処理にかかる時間を計測することができます。
var measure = performance.getEntriesByName('something-time') [0]; // ミリ秒単位の処理時間 console.log(measure.duration); // 測定結果の種類。ここでは'measure'が⼊る。 console.log(measure.entryType); // 宣⾔した計測地点間の名前。ここでは'something'が⼊る。 console.log(measure.name); // 開始時点でのミリ秒単位のタイムスタンプ console.log(measure.startTime);3. Resource Timing APIによる計測
個別のリソースの取得にかかる時間を知りたい場合はこれがオススメ。
// ここで取得したPerformanceResourceTimingオブジェク トの配列には、現在のドキュメント内で読み込まれ たすべてのリソースの情報が含まれています。 var entries = window.performance.getEntriesByType('resource'); // リダイレクト処理にかかった時間 console.log(entry.redirectEnd - entry.redirectStart); // 接続の確⽴にかかった時間 console.log(entry.connectEnd - entry.connectStart); // ドメイン名解決にかかった時間 console.log(entry.domainLookupEnd - entry.domainLookupStart);3. パフォーマンス診断ツールの利⽤
パフォーマンス診断ツールとしてオススメは以下2つ
1. Audits ※1 2. PageSpeed Insights、 Lighthouse ※2※1.AuditsとはGoogle ChromeのChrome DevToolsにはAuditsとい うパフォーマンス上の課題を検出する機能 ※2.PageSpeed Insightsは実際のページを読み込んで、課題を検出します。Auditsとは違ってオンライン上で利⽤します■ Audits利用方法
https://qiita.com/fumihiko-hidaka/items/d1355ce4ea34975ad3bcパフォーマンス診断ツールを利用する最大の理由は、改善案を提示してくることです。
どのようチューニングをそもそも始めれば良いのか迷う場合は導入してみるのアリだと思います。4. パフォーマンスの継続的監視
ウェブサイトのパフォーマンスを改善したからといって、それでOKということではありません。
継続的な監視を行い、いつの間にかパフォーマンスが劣化して問題が起きていたということを防ぐことが大事です。
定期的に調査を行いましょう。
オススメなのがパフォーマンス監視ツールのNewRelicBrowserというサービスです。
これを導入することで、パフォーマンスの見える化を実現します。
仕組みとしてはNew Relicの提供するJavaScriptを ウェブサイトに埋め込むと、Navigation Timing API などで得た計測値をNew Relicのサーバーへと送信して、開発者は送信された結果をダッシュボードで確認できます。
■ NewRelic公式サイト
https://newrelic.com/products/browser-monitoringまとめ
ここまでの内容を理解すれば、WEBフロントエンドパフォーマンスの最低限の知識は理解していると思います。
発展編も内容まとめて、そのうち公開します。
- 投稿日:2020-08-24T16:48:02+09:00
【JavaScript】基礎
1.switchで条件分岐
if文で条件の分岐が多数になるときに、switchを使用すればもっと簡単に書くことができます。
// 見本 変数名 = "値" switch (変数名) { case "値": 処理 break; case "値": 処理 break; case "値": 処理 break; default: 処理 break; }// 信号機の色別で出力内容を条件分岐してみます color = "red" switch (color) { case "red": console.log("STOP!!") break; case "yellow": console.log("be careful!!") break; case "blue": case "green": // caseは複数指定することもできます console.log("GO!!") break; default: // 条件のどれにも当てはまらなかった場合にdefaultで指定した内容が出力されます console.log("Not applicable!!") break; // 結果としてはredのSTOPが出力されます }2.whileのループ処理
while文は指定した条件が満たされるまで、ループ処理を繰り返して値を出力してくれる便利なプロパティです。
// 下記はiが1ずつ増えて100になるまでiの値を繰り返し出力するという命令です let i = 0; while (i <= 100) { console.log(i); i++; // この記述がないと無限に処理が実行されるので注意しましょう! }3.forのループ処理
for文はwhile文をさらに簡略化した記述ができるプロパティです。
// 見本 for (let 初期値を代入した変数名; 条件式; 変数の再代入) { console.log(i); }// 下記はiが1ずつ増えて100になるまでiの値を繰り返し出力するという命令です for (let i = 0; i <= 100; i++) { console.log(i); }4.テンプレートリテラル記法
下記のような記述で、文字列と変数を接続することができます。
const animal = "猫"; // シングルクオーテーションの代わりにバッククオートで${}に囲むことで文字列と変数を接続できます console.log(`この動物は ${animal} です` ); // 出力内容:この動物は猫です5.continueとbreak
continueは対象の値をスキップしたいときに、
breakは対象の値で処理を停止させたいときに使用しますfor文を使って見ていきます。
for (let i = 1; i <= 10; i++) { if (i === 4) { continue; } console.log(i); } // この条件式なら、1~10の出力の中で4の値のみスキップされますfor (let i = 1; i <= 10; i++) { if (i === 4) { break; } console.log(i); } // この条件式なら、4の値でループ処理が停止するので、3の値までしか出力されません6.関数
// 引数にはデフォルトで値を入れておくこともできる function 関数名(引数) { 処理 } // 呼び出す 関数名(引数);7.関数宣言と関数式の違い
6で紹介した関数の記述方法は関数宣言と言われています。
その他に関数宣言を変数に代入する関数式というものがあるので紹介しつつ違いを明確にしていきます。// 関数宣言 function 関数名(引数) { 処理 } 関数名(引数);関数式とは、関数を定数に代入することを言います。
// 関数式 const 定数 = function(引数) { // 関数名がいらなくなります 処理 }; // 定数に代入しているので末尾には;が必要です // 呼び出すときは定数名になります 定数名(引数);8.アロー関数
関数をもっと短く記述することができる記法です。
const 定数 = (引数) => { // functionがいらなくなり、=>が必要 return 処理 };また、引数が1つの場合やreturnの処理が1つの場合はもっと簡易的に記述することができます。
const 定数 = 引数 => 処理; // ()も{}も必要なくなります以上
- 投稿日:2020-08-24T16:21:28+09:00
summernoteで動画ファイルをアップロードする
はじめに
summernoteの画像オプションにはファイルをアップロードして挿入する方法とURLから挿入する方法の2種類がありますが、動画についてはURLから挿入する方法しかありません。
ファイルをアップロードするプラグインはいくつかありますが、ファイルをアップロードしてエディタ内に動画を埋め込みたいという要望にうまくマッチするものがありません。
summernoteのコアファイルは弄りたくないので、画像オプションを参考にしつつ既存の動画オプションを拡張する形で動画ファイルのアップロードができるプラグインを作る事にしました。ここではプラグイン作成手順の簡単な説明と、プラグインの利用方法について備忘録として残したいと思います。
なお、作成したプラグインのソースはGitHubに上げています。
summernote-video-uploadプラグイン作成
プラグインの作成は基本的に公式サイトの通り進めればよいのですが、コールバック関数の設定については記載がありません。
アイコンのWEBフォントと同じくプラグインオプションとして設定することができます。$.extend($.summernote.options, { videoUpload: { icon: '<i class="note-icon-video"></i>' }, callbacks: { onVideoLinkInsert: null, onVideoUpload: null, onVideoUploadError: null } });動画オプションメソッド
コアファイルから移植する動画オプションのメソッドは下記の4種類です。
show、showVideoDialog、createVideoNode、readFileAsDataURL特にcreateVideoNodeメソッドでは動画のURLを解析して、YoutubeであればYoutube用のプレイヤーを、ViemoであればViemo用のプレイヤーを生成しています。
埋め込まれるプレイヤーの縦横サイズを変えたい場合にはこのメソッドを変更します。$video = $('<iframe>').attr('frameborder', 0) .attr('src', '//www.youtube.com/embed/' + youtubeId + (start > 0 ? '?start=' + start : '')) .attr('width', '640') .attr('height', '360');画像オプションのメソッド
動画ファイルをアップロードするため、下記の4種類の画像オプションのメソッドを流用します。
insertImage、createImage、insertImagesAsDataURL、insertImagesOrCallback
それぞれ下記のように名称を変更しつつ、コールバック関数を利用するために必要なメソッドを追加しました。
insertVideo、createVideo、insertVideosAsDataURL、insertVideosOrCallback、insertVideoLinkOrCallbackまずshowメソッド内でファイルのURLが入力されたのかファイルがアップロードされたのかを判定し、
URLであればinsertVideoLinkOrCallbackメソッド、アップロードならinsertVideosOrCallbackメソッドを呼び出します。
insertVideoLinkOrCallbackメソッドとinsertVideosOrCallbackメソッドではその名の通りコールバックが指定されているかを判定します。その後insertVideosAsDataURLメソッド、insertVideoメソッド、createVideoメソッドと続いて、各メソッド内でバリデーションチェックなど行いつつcreateVideoNodeメソッドで動画プレイヤーの生成をするのですが、
画像オプションを流用しているため、コールバック関数を利用しないとBase64エンコードされた動画が埋め込まれる事となります。数MB程度であればいいですが、数100MBの動画をBase64エンコードして埋め込むのは怖いですね。
コールバック関数の利用
summernoteの呼び出し時に下記のように記述してコールバック関数を指定します。
$(function() { $('.summernote').summernote({ lang: "ja-JP", toolbar: [ ['insert', ['videoUpload']] ], callbacks: { onVideoUpload: function(files) { sendVideoFile(files[0], $(this)); } } }); });下記のようにして利用できます。
サーバー側で処理した後、動画ファイルのURLをinsertVideoLinkOrCallbackメソッドへ返してやる事で、エディター内にプレイヤーを埋め込むことができます。function sendVideoFile(file, $editor) { $.ajax({ type: 'POST', url: 'xxxxx', data: { fileSize: file.size, fileName: file.name, fileType: file.type }, beforeSend() { $editor.summernote('videoUpload.showVideoUploadMessage', '動画をアップロードしています。'); } }) .then(function(data) { $editor.summernote('videoUpload.insertVideoLinkOrCallback', data.videoUri); }) .catch((...args) => { console.log('error'); }) .then(function() { console.log('finish'); }); }おわりに
実はこのプラグイン、エラー処理については微妙なところがあります。
summernoteのデフォルトでは画像オプションも動画オプションも、エラーが発生した場合はモーダルウィンドウを閉じて処理が終了するだけで、特にメッセージが表示されたりなどはしません。
動画ファイルは重くなりがちなのでエラーが発生した場合には何らかのメッセージを表示してやりたいところですが…モーダルウィンドウを開いたまま処理を止めてしまうと、再度ファイルをアップロードしてもうまく動いてくれず、デフォルトのままモーダルウィンドウを閉じています。その辺りは改善の余地がありますね。
- 投稿日:2020-08-24T15:38:33+09:00
TypeScriptにおける async / await の使い方(非同期 / 同期処理)
TypeScript上で非同期処理を挟んでみるところに遭遇したがうまく型付けできず改めて基礎を振り返ると同時に記事にまとめてみた。
なお、async / await は TypeScript1.7以降に対応している
(ただし 1.7v は es6。 2.1v から es5/es3 に対応)。前置き(1/2) 前提として Promise の理解が必要
もう知っている方は飛ばしてもられば。Promiseの動きが分かっていないとTypeScriptの async / await は理解しづらい。
ご存知かもしれないが、Promiseに対する async / await は Promise を読み書きしやすいよう別の構文に置き換えたもの、つまりシンタックスシュガーである。画像のようにVSCodeにおけるasync関数の型の推測では返り値に Promise が読み込まれていることがわかる。
前置き(2/2) Promiseってなんだっけ(復習)
repairPromise.jsconst repairPromise = new Promise((resolve, reject) => { try { setTimeout(() => resolve('修理しました'), 3000); } catch (e) { reject('修理できませんでした'); } }); const paymentPromise = new Promise((resolve, reject) => { try { setTimeout(() => resolve('送金しました'), 500); } catch (e) { reject('送金できませんでした'); } }); console.log('進捗どうですか?') repairPromise .then(res => { console.log(res); return paymentPromise; }) .then(res => console.log(res));電気屋さんの修理と支払いのPromiseチェーンを書いた。
「電気屋さんに修理を約束した(Promise)」→「修理できた(resolve)」→「次の処理(支払いPromiseへ)」
もしくは
「電気屋さんに修理を約束した(Promise)」→「修理できなかった(reject)」→「エラー処理(catch)」
上例のようにPromiseを用いることで電気屋さんの修理という処理が実行した後でないと resolve も reject という第二の処理が実行できない。必ず順番に行う必要がある。repairPromise.jssetTimeout(() => resolve('修理しました'), 3000); setTimeout(() => resolve('送金しました'), 500); //Promiseでないなら先に処理を終えているもしも Promise で非同期処理を宣言していなければ、setTimoutが500であるpaymentPromiseが先に実行されてしまう。
JavaScript(nodeJS)は非同期処理で動く。非同期の処理が階層として深くなってしまう(ネストし過ぎる)と電気屋さんに修理を果たしてくれた後に実行したいこと(例えばお金を支払う)を先に処理してしまうような思いがけない順番の挙動となることがある。
そうならないため処理を Promise として宣言することが必要。宣言した処理が完遂すると次の処理を then で実行する。通常の async / await の場合
先ほどの処理を async / await に置き換えてみた。 await として呼び出す関数はPromiseである。
repairPromise.jsconst asyncFunc = async() => { console.log('進捗どうですか'); try{ const msg1 = await repairAsync(3000); console.log(msg1); const msg2 = await paymentAsync(500); console.log(msg2); } catch(e) { console.log(e); } }; const repairAsync = (ms) => new Promise(resolve => { setTimeout(() => { resolve('修理しました') },ms); }); const paymentAsync = (ms) => new Promise(resolve => { setTimeout(() => { resolve('送金しました') },ms); }); asyncFunc();蛇足として、先ほどと同じ処理だがawaitをただ列挙するだけだと、最初のPromiseオンリーとは異なり同期的な処理となる。つまり、1つ目のawaitが終わるまで2つ目のawaitで処理を進めておくことができない。
setTimeoutの処理を1つずつにしか処理できない。async / await を最小構成で表すと
画像のように Promise処理 に対して await をつけてあげることで同期的に処理することができる。
asyncMinimum.jsconst asyncTest = async() => { await new Promise(resolve => setTimeout(resolve, 3000)); console.log('finish'); } asyncTest();本題:TypeScriptで async / await の使い方
Promiseの説明で前置きが少し長くなったがここで先ほどのコードを async / await を書いてみた
repairPromise.tsconst asyncFunc = async(): Promise<void>z => { console.log("進捗どうですか"); try { const msg1 = await repairAsync(3000); console.log(msg1); const msg2 = await paymentAsync(500); console.log(msg2); } catch (e) { console.log(e); } }; const repairAsync = (ms: number): Promise<string> => new Promise((resolve) => { setTimeout(() => { resolve("修理しました"); }, ms); }); const paymentAsync = (ms: number): Promise<string> => new Promise((resolve) => { setTimeout(() => { resolve("送金しました"); }, ms); }); asyncFunc();(codesandbox: https://codesandbox.io/s/boring-shadow-1pulk?fontsize=14&hidenavigation=1&theme=dark )
async / await と Promise の関係がわかれば特に難しいことはない。
resolveとしての返り値の型が Promise と、ジェネリック型となっている。使い方と言うまでもないかもしれない。Promiseのジェネリック型について
ジェネリクスは型を生成するテンプレを作ることができるTypeScriptの機能。
型を入れる前はPromiseで表されるのだが、はもちろんの略称。
ジェネリック型についてはTypeScript Deep Diveが参考になる。
- 投稿日:2020-08-24T14:24:15+09:00
【JavaScript】オブジェクト配列を特定のキーでソートする方法
想定
例えば以下のような配列があったとして、コレを
createdAt
の値で並び替えたい。const array = [ {name: '名前1', price: 100, createdAt: '2020-08-11'}, {name: '名前2', price: 200, createdAt: '2020-08-03'}, {name: '名前3', price: 400, createdAt: '2020-08-24'}, {name: '名前4', price: 130, createdAt: '2020-08-31'}, {name: '名前5', price: 200, createdAt: '2020-08-19'}, {name: '名前6', price: 800, createdAt: '2020-08-22'}, ];方法
こうすると並び替えできる。
const newArray = array.sort((a, b) => ((a.createdAt > b.createdAt) ? -1 : 1));
- 投稿日:2020-08-24T14:18:57+09:00
コンポーネント内部での画面遷移 Vue.js備忘録 vol.02
はじめに
前回に引き続き、vue.jsを触ってみて詰まった所などを備忘録としてまとめてみます。
Vue.js(SPA)におけるブラウザバック問題
SPAでの実装に慣れている方からすると初歩的なミスかもしれないが…
このような画面遷移を行なっていた時/hoge
⇨/fuga
⇨/fuga?page=2
ブラウザバックを行うと/fuga?page=2
から/hoge
に戻ってしまっていた。考えられる原因
ページネーションでの遷移が
history.replaceState()
だった(現在の履歴エントリーを新しい履歴エントリーに置換する設定)
history.pushState()
にしたらいけるのでは!?!?history.pushStateにしたものの…
URLは
/fuga?page=2
⇨/fuga
へ変わるが、画面が再描画されない!!
今回作成していたページはAPIを叩いて一覧表示していたため、URLのパラメータの変更を検知して再度APIを叩く処理を書く必要がありました。対処
/fuga
⇨/fuga?page=2
や、/fuga?page=2
⇨/fuga
への遷移は同じコンポーネントを描画するため、同じコンポーネントインスタンスが再利用されます。古いインスタンスを破棄し、新しいものを生成するよりは効率的なのですが、同時にコンポーネント自体のライフサイクルが呼ばれないことも意味しています。
上記の問題を解消するためにvue-routerのナビゲーションガードを使用しました。
beforeRouteUpdate
でパラメータの変更を検知できます。index.jsbeforeRouteUpdate (to, from, next) { // ルート変更に反応する if(to.query !== from.query){ this.research(to.query.page);//APIを叩く処理 } next(); }パラメータの変更を検知し、再度APIを叩く処理を入れることで解消することができました!
最後に
今回は初歩的なミスではありましたが、もし同様のことでお困りでしたら参考になればと思います。
もしこれより良い方法などあればぜひ教えてください!
(vue-routerの公式にしっかり説明が書いてあったのでちゃんと読んでおけばよかったな〜)
- 投稿日:2020-08-24T14:13:55+09:00
【Nuxt.js】Nuxt実践編:v-model + Vuex + firebase
? この記事はWP専用です
https://wp.me/pc9NHC-yW前置き
v-modelで直接storeを書き換えて
それをfirebaseへ送信してみます?ですが実際に使う場面はほぼありません?笑
というのも、
firebaseを使って入力した値を送信さえできれば良いからです。
取得も簡単にできるので
わざわざVuexを使ってstoreに保存する必要がないのです…!なので
仕組みを見て「ふーんなるほど?」と
分かってもらえたら良いなと思います??v-model, computed, vuex, firesbase(firestore)の
解説記事を読んだ方なら、
当たり前じゃん❗️
と思うかもしれません!
それぞれの解説記事もあるので
リンクを貼っておきます??⬇️解説記事はこちら
v-model: https://wp.me/pc9NHC-kI
computed: https://wp.me/pc9NHC-wY
Vuex: https://wp.me/pc9NHC-gl https://wp.me/pc9NHC-dH
firestore: https://wp.me/pc9NHC-g4完成コード
まずは完成コードからお見せしちゃいます?
この後に、ここに至るまでの考え方などを書いていきます。
そちらを見ながらやってみてください??firestoreとの連携などは前置きリンクからご覧ください?
plugin.firebase.jsimport firebase from "firebase/app" import 'firebase/firestore' if (!firebase.apps.length) { firebase.initializeApp({ apiKey: "貼り付け", authDomain: "貼り付け", databaseURL: "貼り付け", projectId: "貼り付け", storageBucket: "貼り付け", messagingSenderId: "貼り付け", appId: "貼り付け", measurementId: "貼り付け" }) } export default firebaseindex.vue<template> <div class="page"> <input type="text" v-model="name" > <input type="text" v-model="email" > <p>{{ name }} {{ email }}</p> <button @click="submit">submit</button> </div> </template> <script> export default { computed: { name: { get() { return this.$store.getters['name'] }, set(value) { this.$store.commit('updateName', value) }, }, email: { get() { return this.$store.getters['email'] }, set(value) { this.$store.commit('updateEmail', value) }, }, }, methods: { submit () { this.$store.dispatch('submit') } }, } </script>store.index.jsimport firebase from '@/plugins/firebase' export const state = () => ({ name: "名前", email: "email", }) export const getters = { name: state => { return state.name }, email: state => { return state.email }, } export const actions = { submit ({ state }) { const db = firebase.firestore() let dbUsers = db.collection('users') dbUsers .add({ name: state.name, email: state.email, }) .then(ref => { console.log('Add ID: ', ref.id) }) }, } export const mutations = { updateName(state, newName) { state.name = newName }, updateEmail(state, newEmail) { state.email = newEmail }, }経緯
それでは解説をしていきます?
? 続きはWPでご覧ください?
https://wp.me/pc9NHC-yW
- 投稿日:2020-08-24T13:27:30+09:00
iframeのsandbox属性の動的設定
sandbox属性とは
sandbox属性とはiframe内の要素に対し制約を設定する機能です。
iframe内でjavascriptの実行を無効などすることができます。
属性は以下のような形式で設定することができます。<iframe sandbox="allow-forms allow-scripts"></iframe>※設定できる属性についてはMDNを参照。
動的設定方法
sandbox属性は属性を変更するだけでは反映されません。sandbox属性の変更を反映させるには、フレームを再読み込みする必要があります。
<iframe src="frame"></iframe>const iframe = document.getElementById('frame'); iframe.sandbox.add("allow-top-navigation"); // この段階では反映されない。 iframe.contentWindow.location.reload(); // ここで反映される。まとめ
如何でしたでしょうか?今回はiframeのsandbox属性の動的設定方法についてまとめて見ました。
今回この記事を作成した理由ですが、sandbox属性を使用する機会があり、属性値を変更したにも関らす、反映され無くてつまったことがあったので作成しました。記事に間違いや追加してほしい事がありましたら、コメントにご記入ください。
- 投稿日:2020-08-24T13:23:06+09:00
【ES6】配列メソッド~filter編~【JavaScript】
filterメソッドとは
与えられた関数によって実装されたテストに合格したすべての配列からなる新しい配列を生成します。
引用:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
噛み砕いていきます。
filter
メソッドは新しい配列を作るメソッド- 何らかの値をテストする関数をコールバック関数として受け取る。
サンプルコード
下記のコードは2桁の数字を取り出すコード。(小数点は想定しない。)
const ary = [1, 10, 99, 100]; const test = (num) => { // 二桁の数値か判定(小数点を想定しない) return num > 9 && num < 100; }; const newAry = ary.filter((num) => { return test(num); }); console.log(newAry); // [10, 99]この場合では
test
関数を別にする意味は薄いんですけど、こういう風に処理を別々にすることで保守性が高まったりします。(多分)
- 投稿日:2020-08-24T13:11:01+09:00
両端からフェードアウトしていくカーテンみたいなアニメーションをcssとjsで作成
右端からフェードアウトしていく風のものはあったのですが両端からは見つからなかったので自作。
白いボックスを両端から出して背景を覆い隠しているだけです。#loading { width: 100vw; height: 100vh; transition: all 1s; background-color: #274bd2; position: fixed; top: 0; left: 0; z-index: 9999; } .fadeout { display: none; } #left_bk,#right_bk { width: 0; height: 100vh; background-color: rgba(255,255,255,0.8); border: none; position: absolute; top: 0; z-index: 2; } #line { width: 3px; height: 100vh; background-color: #274bd2; border: none; position: absolute; top: 0; left: 47.5vw; z-index: 3; } #left_bk { left: 0; } #right_bk { right: 0; } .anime5 { animation: animemove5 0.3s linear forwards; } /*1.5sかけて*/ .anime7 { animation: animemove7 1.5s linear forwards; } @keyframes animemove5 { 0% {} 100% {width: 50vw;} /*50vwまで拡大*/ } @keyframes animemove7 { 0% {} 100% {left: calc(100% - 54px);} }const body = document.getElementsByTagName('body')[0]; function fadeOut() { left_bk.classList.add('fadeout'); right_bk.classList.add('fadeout'); loading.classList.add('fadeout'); } window.onload = function() { setTimeout(cartain, 700); setTimeout(blueline, 1000); setTimeout(fadeOut, 2000); }; function cartain() { left_bk.classList.add('anime5'); right_bk.classList.add('anime5'); $("#loading").animate({ opacity: 0 }, { duration: 500, easing: 'linear'}); } function blueline() { line.classList.add('anime7'); }<div id="loading"> <div id="left_bk"></div> <div id="right_bk"></div> </div> <div id="line"></div> <script src="loading.js"></script>
- 投稿日:2020-08-24T12:59:50+09:00
【ES6】配列メソッド~map編~【JavaScript】
mapメソッドとは
- 配列の要素一つ一つに処理を加える
- その値で新しい配列を作る
サンプルコード
const oldAry = [1, 2, 3, 4, 5]; const newAry = oldAry.map((value) => { return value * 5; }); console.log(newAry); //=> [5, 10, 15, 20, 25]使いどき
引数にコールバック関数を取ることができるので、
- 配列の要素に処理を加える関数(コールバック)
- mapメソッドで新しい配列を作成。const ary = [hoge, hoge]; // 処理したい要素の配列 const fun = (value) => { //処理内容 }; const newAry = ary.map((value) => { fun(value); });要素を処理する関数と新しい配列を作る関数を別々に管理することができますね?
- 投稿日:2020-08-24T12:49:45+09:00
nuxtjsでwebpackのcacheGroupsを使ってJavaScriptとcssの分割方針を変える
Webページの高速化のする手法の一つにJavaScriptやcssなどのアセットを小さくして、細かいファイルにしてCDNにアップロードするというものがあります。
HTTP/2では1つのコネクション上で複数並列に扱うことができるので、ファイルを一つにまとめて通信回数を減らすより、ファイルサイズを小さくして平行に複数ファイル取得するほうが有利です。
nuxt.jsでは
nuxt.config.js
ファイルのbuild部分がそのままwebpackの設定となるので、ここに設定を追加していくことでwebpackの設定を変更しビルドの方針を変えることが出来ます。全部一つのファイルにまとめる場合
nuxt.config.js{ ... (省略) ... build: { optimization: { cacheGroups: { styles: { name: 'styles', minChunks: 1, // 1箇所以上で使われていたらlibraryとしてchunkに含める chunks: 'all' enforce: true // ファイルサイズの制限を無視してchunkをまとめる } } } } }この設定では1箇所以上で使われているファイルをビルド後のファイルサイズを無視してchunkに含めるということになるので、全てのコードが1つのchunkに詰め込まれて同じファイルになります。当然ファイルは大きくなります。
ファイルを複数に分割する場合
nuxt.config.js{ ... (省略) ... build: { optimization: { cacheGroups: { styles: { name: 'styles', minChunks: 5, // 5箇所以上で使われていたらlibraryとしてchunkに含める chunks: 'all' enforce: false // ファイルサイズの制限をできるだけ守るように分割する minSize: 30000, // 30kbを超えるようにファイル分割する maxSize: 1000000, // 100kbを超えないようにファイル分割する } } } } }このように設定するとファイルのサイズが30kb〜100kbの間に収まるように出来るだけ分割してビルドしてくれます。
- 投稿日:2020-08-24T12:44:03+09:00
PDCAサイクルとOODAループを併用する方法
今回の記事では、PDCAサイクルとOODAループの違いについて解説し、併用する方法について紹介したいと思う。
インターネット上の記事では、OODAループとPDCAサイクルを比較する論調が目立つがその考えは誤りである。
PDCAサイクルは計画、実行、チェック、改善のループを回すことによって、全体的なパフォーマンス向上を目的としている。
OODAループは、観察、判断、意識決定、手段のループを行うことで、人間が変化が激しい現実の世界に適応する思考のプロセスをモデル化し、良い判断を行うことを目的としている。
PDCAサイクルやOODAループは人間が無意識に行っている思考のプロセスを客観化、モデル化したものである。
そのため、PDCAサイクルとOODAループは人間の思考プロセスで無意識的に併用している。PDCAサイクルとは
PDCAサイクルは戦略レベルの思考である。
1920年代において「戦略」について明確な定義を行ったのは、ロシア帝国時代とソビエト時代の将軍であるアレクサンドル・スヴェチンである。
スヴェチンが定義した「戦略」の定義は以下の内容となっている。
戦争の準備と作戦のグループ化を組み合わせて、軍が戦争によって提唱した目標を達成する技術そして、1980年代で最も適切に戦略について解説した著書「戦略論の原点」を発表したアメリカ海軍の将軍であるジョセフ・カルドウェル・ワイリーは「戦略」を以下のように定義している。
戦略とは「何かしらの目標を達成するための一つの「行動計画」であり、その目標を達成するために手段が組み合わさってシステムと一体となった、一つの「ねらい」である」戦略家であるアレクサンドル・スヴェチンとジョセフ・カルドウェル・ワイリーは戦略を目標を達成するための行動計画と定義している。
PDCAサイクルとは、戦略のパフォーマンスを検証、改善することによって、よりよい戦略を作り出すフィードバックループを構築するためのモデルと定義すること的確である。OODAループとは
OODAループとは、アメリカの航空戦略家であるジョンボイドが朝鮮戦争の航空戦、ナチスの電撃戦を研究し、変化に適応する人間の意識決定のスピードが勝敗が分けたと結論づけた。
人間の意識決定のプロセスを可視化したのが以下のOODAループである。
観察(Observe)- 情勢への適応(Orient)- 意思決定(Decide)- 行動(Act)通常の人間の認知範囲は作戦レベルに対応していると考えられている。
そして、この思考の上位に戦略レベルが下位に戦術レベルが存在している。
現場レベルの作業に従事している人間は、変化が激しい情勢に柔軟に対応しなければならない。
OODAループとは、人間が現実の世界に対応するために思考パターンをモデル化したものである。
そのため、OODAループとは作戦レベル、戦術レベルの思考パターンと考えられる。戦略レベルの行動のフェーズについて
スヴェチンは「戦略」の行動を以下のフェーズごとに分類した
1.計画フェーズ・・・目標を達成するめの行動計画を立てるフェーズ。
2.開発フェーズ・・・目標を達成するめの手段を開発するためのフェーズ。新しいアイディアの作成、アイディアに基づく手段の作成、手段を運用する人の教育、もの or サービスの作成などが該当
3.配置フェーズ・・・開発フェーズで作成したオブジェクトを配置するフェーズ。オブジェクトの管理 or 保存、人材の配置、ものの運搬経路の設定、通信経路の設定などが該当
4.運用フェーズ・・・配置フェーズで作成したオブジェクトを作戦レベルで運用するフェーズ。開発フェーズで作られたアイディアを組み合わせ、目標を達成する。
PDCAサイクルは戦略のプロセスを検証、評価、改善し、次の戦略へフィードバックすることを目的としている。PDCAサイクルとOODAループの併用
PDCAサイクルとOODAループの併用を図式化したのが以下の画像である、
戦略レベルで作成した行動計画がPDCAサイクルによってフィードバックされ、次の戦略の行動計画へ繋がり、OODAループが変化が激しい変化に適応していくためのサイクルだと理解することができる。
PDCAサイクルとOODAループを比較した際に違和感が発生するのは、比較している行動モデルの階層が一致しないためである。まとめ
PDCAサイクルとOODAループはモデルを提供することによって、人間に共通した認識を提供してくれる。
しかし、具体的な手段までは定義していないため、PDCAサイクルとOODAループを適用するためには、組織に合致した手段、チェック方法を独自に考えなければならない。
PDCAサイクルとOODAループを適切に運用できないケースとは、組織がPDCAサイクルとOODAループに合致した手段を用意できないことが原因だと思われる。
- 投稿日:2020-08-24T12:40:57+09:00
Amazon Chime SDKでスライドをバーチャル背景に設定する
この記事はこちらでも紹介しています。
https://cloud.flect.co.jp/entry/2020/08/24/123607前回は、Amazon Chime SDKでビデオセッションの上限(16セッション)を超えて映像配信する実験をご紹介しました。
https://qiita.com/wok/items/683561486c7b79c25edc今回もAmazon Chime SDKについて、最近他のビデオ会議ツール等で話題になっていた機能を実現する方法をご紹介したいと思います。
今回ご紹介するのは、パワーポイントなどのスライドをバーチャル背景にする方法です。
最初に大きな話題になったのは、mmhmmでしたね。その後、Zoomでも同様の機能がリリースされました。リモートで行うプレゼンに臨場感が出て良いと思います。これをAmazon Chime SDKを用いて実現する方法をご紹介します。
Amazon Chime SDK(js)を使うので、特にソフトウェアをインストールすることなく使えちゃいます!!
また、スライド以外にも、画面共有可能なウィンドウをすべて背景にすることができます。
なので、例えばエディタとスピーカーを表示しながらライブコーディング、なんてことも出来たりします。Amazon Chime SDKでカメラの映像を送信する方法
以前、バーチャル背景の記事でもご説明した内容ですが、チュートリアルやサンプルなどではカメラの映像を送信する際の一般的な方法としてカメラデバイスのIDを登録する方法が紹介されています。しかし、実は同一のメソッドでMediaStreamを登録することもできるようになっています。
つまり、実はAmazon Chime SDKではMediaStreamさえ取得できれば、何でもシェアできるという柔軟な設計になっているのです。
たとえば、HTMLCanvasElementからはMediaStreamが取得できます。このため、HTMLCanvasElementになにか描画して、それを配信することが出来ます。
上述の記事では、これを利用してバーチャル背景は実現しました。
今回も、これを利用してスライドをバーチャル背景にします。スライドの画像取得
バーチャル背景では、カメラの映像をBodyPixを用いて人物の領域と背景の領域を識別し、背景部分を指定された背景画像の映像に置き換えてHTMLCanvasElementに描画していました。
今回は、この背景画像を、パワーポイントなどのアプリケーションのウィンドウから取得します。下記のようにを呼び出すことで、アプリケーションの映像をMediaStreamとして取得できます。これをHTMLVideoElementのソースに設定した後に、カメラ映像と同じサイズのHTMLCanvasElementにフレームを描画します。そして、カメラの映像のフレームごとに、このHTMLCanvasElementのイメージをキャプチャして背景画像として用います。navigator.mediaDevices.getDisplayMedia().then(media => { ... })処理の流れを纏めると次のようになります。図中上部は前述の通常のバーチャル背景の処理の流れです。図中下部が今回の処理です。赤字部分が変更点となります。
バーチャルフォアグランドもやってみた。
ここまでの説明で、Amazon Chime SDKでは、様々な加工をした映像を配信できることがおわかりになるかと思います。
例えば、下図は人物を線画にしたり、Asciiアートにしたりしてみたものです。
バーチャル背景(バックグラウンド)ではらぬ、バーチャルフォアグラウンドといったところでしょうか。このように、Amazon Chime SDKでは、カメラの映像配信用にMediaStreamを受け付けることで、さまざまな拡張が可能になっています。楽しいですね。
デモ
今回ご紹介したものと同等のロジックを組み込んだサイトを用意しました。
https://flect-lab-web.s3-us-west-2.amazonaws.com/001_virtual_background/index.html
npmパッケージ
また、簡単に使えるようにnpmパッケージも作ってみました。
https://www.npmjs.com/package/local-video-effector
FLECT Amazon Chime Meeting
また、ビデオ会議の新機能のテストベッドとしてビデオ会議室システムを作成しており、リポジトリを公開しています。
今回ご紹介した機能も、こちらに実装されていますので、興味があればアクセスしてみてください。https://github.com/FLECT-DEV-TEAM/FLECT_Amazon_Chime_Meeting
最後に
今回は、Amazon Chime SDKでスライドをバーチャル背景に設定する方法をご紹介しました。
また、この方法は、画面共有可能なアプリケーションであれば何でも背景に設定することができます。
このため、例えばライブコーディングを配信してみるなど、様々な活用の仕方が考えられると思いますので、是非お試しください。次回は、またAmazon Chimeで遊ぶか、以前紹介したマルチバーコードリーダの技術的な内容をご紹介するかをしようと思ってます。
では。
- 投稿日:2020-08-24T11:52:08+09:00
React + SimpleBar: スクロールバーのスタイルをカスタマイズする
SimpleBarはスクロールバーをカスタマイズするライブラリです。スクロールバーを独自につくるのではなく、CSSのスタイルを割り当てるので、おかしな挙動は起こらず、ネイティブなスクロールのパフォーマンスが保たれます。あくまで、スクロールバーの見栄えを変えるだけです。
デザインはCSSで定める
SimpleBarは純粋なCSSでスクロールバーのスタイルを定めます。CSSで与えられるスタイルでさえあれば、自由にカスタマイズできるということです。また、macOSとWindowsで同じ見た目になるのも大きな魅力といえます。
軽量なライブラリ
6KBのとても軽いライブラリです。JavaScriptはスクロールの動きそのものには触れません。ネイティブな動きとパフォーマンスが得られます。
モダンブラウザをサポート
ChromeとFirefox、Safariなどのモダンブラウザに加え、Internet Explorer 11をサポートします。
ライブラリの概要はドキュメントとデモページでお確かめください。本稿と同じタイトルの「JavaScript + SimpleBar: スクロールバーのスタイルをカスタマイズする」でつくったつぎの作例は、標準のJavaScriptコードでSimpleBarのスタイルを割り当てました。「Left Column」にマウスポインタを重ねると、グラデーションのスクロールバーが現れます。今回のお題は、React用のSimplebarReactで同じサンプルをつくることです。
See the Pen JavaScript + SimpleBar: Customizing scrollbar style by Fumio Nonaka (@FumioNonaka) on CodePen.
本稿の作例は、Create React Appのひな形アプリケーションをもとにつくります。ひな形のつくり方については「Reactアプリケーションのひな形をつくる」をお読みください。
インストール
まず、SimpleBarをインストールします(「Installation」)。SimplebarReactもSimpleBarのCSSを使うからです。npmまたはyarnでインストールしてください。
npm install simplebar --save
yarn add simplebar
つぎに、SimplebarReactのインストールです。やはり、npmまたはyarnで行います(「Installation」)。
npm install simplebar-react --save
yarn add simplebar-react
基本となるページの組み立て
ページを構成する要素は大きく3つ、ヘッダと左カラム、そしてメインコンテンツです(図001)。また、Bootstrap 4.5を用いました。ただし、本稿ではCSSの説明は基本的に省き、SimpleBarの扱いに関わる定めだけ解説することにします。確かめたい方は、最後に掲げるCodeSandboxのサンプルまたはGithubのソースをご覧ください。
図001■ヘッダと左カラムにメインコンテンツで組み立てられたページ
以下のコードは、アプリケーション(
src/App.js
)に、それぞれヘッダ(src/components/Header.js
)と左カラム(src/components/LeftColumn.js
)およびメインコンテンツ(src/components/MainContents.js
)を静的にレイアウトしたモジュールの中身です。src/App.js
がsimplebar/dist/simplebar.min.css
を読み込んでいます。SimplebarReactのJavaScriptライブラリを
import
するのは、スクロールバーをカスタマイズする左カラムのモジュール(src/components/LeftColumn.js
)です。なお、リストの連番項目は、メソッドArray.from()
とArray.prototype.map()
でつくりました。興味のある方は「ECMAScript 6のArrayに関わる構文を試す」をお読みください。src/App.jsimport React from "react"; import "bootstrap/dist/css/bootstrap.min.css"; import "simplebar/dist/simplebar.min.css"; import "./App.css"; import Header from "./components/Header"; import MainContents from "./components/MainContents"; import LeftColumn from "./components/LeftColumn"; function App() { return ( <div className="App"> <Header /> <div className="container-fluid d-flex px-0"> <LeftColumn /> <MainContents /> </div> </div> ); } export default App;src/components/Header.jsimport React from "react"; const Header = () => { return ( <header id="header" className="text-white bg-primary w-100 p-2 d-flex"> <h1>Header</h1> </header> ); }; export default Header;src/components/LeftColumn.jsimport React from "react"; const LeftColumn = () => { return ( <div id="left-column" className="bg-light p-2"> <h3>Left column</h3> <ul id="list" className="pl-4"> {Array.from(new Array(20), (_, index) => ( <li key={index}>item {String(index + 1).padStart(2, 0)}</li> ))} </ul> </div> ); }; export default LeftColumn;src/components/MainContents.jsimport React from "react"; const MainContents = () => { return ( <main className="px-4 py-2"> <h2>Main contents</h2> <p> <!-- [中略] --> </p> </main> ); }; export default MainContents;ヘッダを上部に固定する
まずは、ヘッダをページ上部に固定するCSSの設定です(
src/App.css
)。位置はposition
プロパティにfixed
を与えて固定します。具体的な置き場所は上部なのでtop: 0
です。すると、<body>
要素の領域に含まれなくなるので、そのままではページの上部がかぶって隠れてしまいます(図003)。src/App.css#header { position: fixed; top: 0; }図002■ページ上部をヘッダが覆ってしまう
<body>
要素のpadding
またはmargin
は、ヘッダの高さ分下げなければならないのです。もっとも、高さはウィンドウ幅やレスポンシブの設定によって変わるかもしれません。動的に定めるべきでしょう。要素の高さは
element.clientHeight
で得られます。要素を参照するフックはuseRef
です(src/App.js
)。参照(header
)は、プロパティでヘッダのコンポーネント(src/components/Header.js
)に渡します。ただし、element.clientHeight
は、読み取り専用プロパティであることにご注意ください。高さの設定には、要素(<div>
)のstyle
属性を用います。src/App.js// import React from "react"; import React, { useEffect, useRef, useState } from "react"; function App() { const [headerHeight, setHeaderHeight] = useState(0); const header = useRef(null); useEffect(() => { const _header = header.current; const setLayout = () => { setHeaderHeight(_header.clientHeight); } window.addEventListener("resize", setLayout); setLayout(); }, []); return ( // <div className="App"> <div className="App" style={{ paddingTop: headerHeight }}> {/* <Header /> */} <Header headerRef={header} /> </div> ); }src/components/Header.js// const Header = () => { const Header = ({ headerRef }) => { return ( <header id="header" ref={headerRef} > <h1>Header</h1> </header> ); };SimpleBarを組み込む
SimpleBarを使う要素には、
overflow
プロパティにauto
を与えてください(src/App.css
)。そのうえで、SimpleBarを設定する要素は<SimpleBar>
に置き替えます(src/components/LeftColumn.js
)。src/App.css#left-column { overflow: auto; }src/components/LeftColumn.jsimport SimpleBar from "simplebar-react"; const LeftColumn = () => { return ( // <div id="left-column" className="bg-light p-2"> <SimpleBar id="left-column" className="bg-light p-2"> {/* </div> */} </SimpleBar> ); };そして、スクロールバーを表示するには、要素に高さを定めなければなりません。
src/App.css#left-column { overflow: auto; height: 400px; /* 高さを定める */ }とはいえ、高さを決め打ちは避けたいところです。すでに、ヘッダの高さはとれるのですから、ブラウザウィンドウのビューポートの高さ(
window.innerHeight
)から差し引けば、左カラムの高さ(leftColumnHeight
)は求まります。src/App.jsfunction App() { const [leftColumnHeight, setLeftColumnHeight] = useState(0); useEffect(() => { const setLayout = () => { setLeftColumnHeight(window.innerHeight - _header.clientHeight); }; }, []); return ( <div className="App" style={{ paddingTop: headerHeight }}> <div className="container-fluid d-flex px-0"> {/* <LeftColumn /> */} <LeftColumn leftColumnHeight={leftColumnHeight} /> </div> </div> ); }src/components/LeftColumn.js// const LeftColumn = () => { const LeftColumn = ({ leftColumnHeight }) => { return ( // <div id="left-column" className="bg-light p-2"> <SimpleBar id="left-column" className="bg-light p-2" style={{ height: leftColumnHeight, }} > {/* </div> */} </SimpleBar> ); };これで、ウィンドウに合わせて左カラムの高さが定まり、スクロールバーは自動表示されるようになりました(図003)。
図003■SimpleBarのスクロールバーが自動表示される
ページ全体をスクロールしたときの不具合を直す
まだ少し、不具合が残っています。ページ全体を下にスクロールしたとき、左カラムがせり上がって、ヘッダにかぶってしまうことです(図004)。
図004■ページを下にスクロールすると左カラムがヘッダにかぶる
左カラム(
src/components/LeftColumn.js
)の垂直位置は固定しなければなりません。やり方は、前述「ヘッダを上部に固定する」と同じです。ただ、CSS(src/App.css
)でなく、style
属性で定めることにしました。src/App.jsfunction App() { return ( <div className="App" style={{ paddingTop: headerHeight }}> <div className="container-fluid d-flex px-0"> <LeftColumn headerHeight={headerHeight} /> </div> </div> ); }src/components/LeftColumn.js// const LeftColumn = ({ leftColumnHeight }) => { const LeftColumn = ({ headerHeight, leftColumnHeight }) => { return ( <SimpleBar style={{ position: "fixed", top: headerHeight, }} > </SimpleBar> ); };もちろん、前述「ヘッダを上部に固定する」と同じように、左カラムがメインコンテンツにかぶってしまいます(図005)。
図005■メインコンテンツが左カラムに隠れてしまう
「ヘッダを上部に固定する」と同じ考え方で、メインコンテンツの左端をカラムの幅だけ右に寄せればよいはずです。けれど、つぎのコードではメインコンテンツの位置がまったく動きません。
src/App.jsfunction App() { const [leftColumnWidth, setLeftColumnWidth] = useState(0); const leftColumn = useRef(null); useEffect(() => { const _leftColumn = leftColumn.current; const setLayout = () => { setLeftColumnWidth(_leftColumn.clientWidth); }; }, [leftColumn]); return ( <div className="App" style={{ paddingTop: headerHeight }}> <div className="container-fluid d-flex px-0"> <LeftColumn leftColumnRef={leftColumn} /> {/* <MainContents /> */} <MainContents leftColumnWidth={leftColumnWidth} /> </div> </div> ); }src/components/MainContents.js// const MainContents = () => { const MainContents = ({ leftColumnWidth }) => { return ( // <main className="px-4 py-2"> <main className="px-4 py-2" style={{ marginLeft: leftColumnWidth }}> </main> ); };src/components/LeftColumn.js// const LeftColumn = ({ headerHeight, leftColumnHeight }) => { const LeftColumn = ({headerHeight, leftColumnHeight, leftColumnRef }) => { return ( <SimpleBar ref={leftColumnRef} > </SimpleBar> ); };SimpleBarコンポーネントをラップする
調べてみると、
SimpleBar
コンポーネントのclientWidth
プロパティ値が0です。SimpleBarは、あくまでスクロールバーのスタイルを整えるためのラッパーだからでしょう。そこで、
Simplebar
コンポーネントをつぎのように<div>
要素で包み、スクロールバーに用いる以外の属性はすべてこの要素に移します。こうすることで、カラムの要素の幅(clientWidth
)が正しく得られるのです。メインコンテンツの左端は、カラムの右端に揃います。src/components/LeftColumn.jsconst LeftColumn = ({ headerHeight, leftColumnHeight, leftColumnRef }) => { return ( <div id="left-column" ref={leftColumnRef} className="bg-light p-2" style={{ position: "fixed", top: headerHeight, }} > <SimpleBar /* id="left-column" ref={leftColumnRef} className="bg-light p-2" */ style={{ /* position: "fixed", top: headerHeight, */ height: leftColumnHeight, }} > </SimpleBar> </div> ); };CSSでスクロールバーのスタイルを変える
SimpleBarのスクロールバーのスタイルは、CSSにより定められています。つまり、見栄えがCSSで変えられるということです。ここでは、スクロールさせるスライダのカラーを、つぎのCSSでグラデーションにしてみましょう(図006)。
src/App.css.simplebar-scrollbar::before { background: linear-gradient(darkblue, skyblue); }図006■メインコンテンツの位置が正しく定まってスライダはグラデーションになった
冒頭の標準JavaScriptのサンプルと同じページをSimplebarReactでつくり、CodeSandboxに掲げました。また、Githubでもソースをご覧いただけます。