- 投稿日:2019-10-28T23:56:24+09:00
WebComponents だけでアプリの骨格を作ってみる
はじめに
WebComponents、何がいいかというとブラウザでネイティブに対応してるとこですね。
そろそろ本家、3rdパーティーともライブラリが充実してきて、Vue とか React とかのフレームワークを使わずに WebComponents だけでいけそうな感じになってきたので、とりあえず下のようなアプリの骨格を作って検証してみます。
オフィシャルの material.io の GitHub にはメジャーな
- material-components-android
- material-components-ios
- material-components-web
- material-components-flutter
の他に
ってのがあります(名前が非常にまぎらわしい><)。GUI コンポーネントとして今回これを使わせてもらいます。
また router として、
を使わせてもらいます。
早速作ってみましょう。
準備
node, npm をダウンロードします
https://nodejs.org
この記事は node v12.4.0, npm 6.12.0 で検証していますディレクトリを掘って cd して node のプロジェクトを作成します。
$ mkdir wc $ cd wc $ yarn inites-dev-server をインストールして動かしておきます。
$ npm install -g es-dev-server $ es-dev-server --node-resolve --watch8000 番のポートが使われてなければ
es-dev-server started on http://localhost:8000
と表示されます。ブラウザでここにアクセスしてください。--node-resolve オプションが何故必要かについて知りたい場合は以下を参照してください。
https://github.com/material-components/material-components-web-components/blob/master/README.md
作成
使うコンポーネントを登録
$ yarn add @material/mwc-drawer $ yarn add @material/mwc-icon-button $ yarn add @material/mwc-top-app-bar $ yarn add @vaadin/routerメイン画面に表示する WebComponent を作成
Main.jsclass Main extends HTMLElement { constructor() { super(); this.attachShadow( { mode: 'open' } ).innerHTML = '<div>MAIN</div>' } } customElements.define( 'jp-main', Main )About.jsclass About extends HTMLElement { constructor() { super(); this.attachShadow( { mode: 'open' } ).innerHTML = '<div>ABOUT</div>' } } customElements.define( 'jp-about', About )まとめあげ
index.html<!doctype html> <html lang=zxx> <title>drawer demo</title> <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Material+Icons&display=block" rel="stylesheet"> <body> <script type="module"> import '@material/mwc-drawer' import '@material/mwc-top-app-bar' import '@material/mwc-icon-button' import './Main' import './About' import { Router } from '@vaadin/router' class App extends HTMLElement { constructor() { super(); this.attachShadow( { mode: 'open' } ).innerHTML = ` <mwc-drawer hasHeader type="dismissible"> <span slot="title">Drawer Title</span> <span slot="subtitle">subtitle</span> <div> <p>Drawer content</p> <a href="/">Main</a><br /> <a href="/About">About</a><br /> </div> <div slot="appContent"> <mwc-top-app-bar> <mwc-icon-button slot="navigationIcon" icon="menu"></mwc-icon-button> <div slot="title">Title</div> </mwc-top-app-bar> <div id="outlet" /> </div> </mwc-drawer> ` const router = new Router( this.shadowRoot.getElementById( 'outlet' ) ) router.setRoutes( [ { path: '/' , component: 'jp-main' } , { path: '/about' , component: 'jp-about' } ] ) const drawer = this.shadowRoot.querySelector( 'mwc-drawer' ) drawer.parentNode.addEventListener( 'MDCTopAppBar:nav' , _ => drawer.open = !drawer.open ) } } customElements.define( 'jp-app', App ) </script> <jp-app /> </body>勘所
navigationIcon
mwc-top-app-bar の navigationIcon スロットに入れられたボタンが押されると、mwc-drawer の parentNode に対して MDCTopAppBar:nav イベントが送られます。(すみません、ドキュメントが見当たりません><、レポジトリの demos/drawer/のあたりを読んで発見しました)
shadowRoot
WebComponent は constructor の中で attachShadow ってやると、shadowRoot が使えるようになります。プライベートな DOM みたいなものです。これを実際の DOM に connect して表示するようになります。
constructor の中ではまだ実際の DOM にコネクトされていないので、shadowRoot に対して getElement... とか querySelector とか発行して、エレメントをとります。shadowRoot に対して、今回の記事では innerHTML をスタティックテキストでセットしているだけですが、実際にはダイナミックに生成したい場合が多いと思われます。そんなときは polymer の lit-element を使うという選択肢があります。lit-html という HTML テンプレートライブラリをラップしたものです。
古いブラウザのサポート
Edge とか IE11 とかをサポートするにはポリフィルが必要です。詳しくは以下を参照してください。
https://github.com/material-components/material-components-web-components/blob/master/README.md
最後に
shadowRoot を更新する際に既存のエレメントを再利用して描画の高速化を図るようなベースクラスをたぶんそのうち誰かが作ってくれる気がします。もし知っていたら教えてください。
- 投稿日:2019-10-28T23:22:33+09:00
Rails5.2 + Vue.js + Dockerでプロジェクトを作成
はじめに
Railsを使っていてVueも一通り学んだのでRailsとVueを使えるように環境構築してみようと思い勉強してみたことをアウトプット。
Rubyのバージョンは2.5.3
Railsのバージョンは5.2.3
DBはMysqlを使用Railsのプロジェクトを作成するディレクトリを事前に作成しておく
$ mkdir <任意のフォルダ名>
フォルダを作成したら作成したディレクトリに移動しておくDocker用のファイルの作成
Dockerのコンテナを作成するためのファイルを作成
Dockerfile
docker-compose.yml
Gemfile
Gemfile.lock
の4つのファイルを作成する。DockerfileFROM ruby:2.5.3 RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - && apt-get update && \ apt-get install -y nodejs --no-install-recommends && rm -rf /var/lib/apt/lists/* RUN apt-get update -qq && apt-get install -y build-essential libpq-dev RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && apt-get install -y yarn RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \ apt-get install nodejs RUN yarn add node-sass RUN mkdir /app WORKDIR /app COPY Gemfile /app/Gemfile COPY Gemfile.lock /app/Gemfile.lock RUN bundle install COPY . /appdocker-compose.ymlversion: '3' services: web: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - .:/app ports: - 3000:3000 depends_on: - db tty: true stdin_open: true db: image: mysql:5.7 volumes: - db-volume:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: password volumes: db-volume:Gemfilesource 'https://rubygems.org' ruby '2.5.3'Gemfile.lock
はbuildした後に自動的に記述されるので空にしておく。(ファイルは必要)この4つのファイルを作成し、先ほど作成したディレクトリに置く。
Railsプロジェクトの作成
次にRailsのプロジェクトを作成するため次のコマンドを実行
$ docker-compose run web rails new . --force --database=mysql
(rails newの . は現在いるディレクトリにそのままプロジェクトを作成するというもの)プロジェクトの作成が終わったら
docker-compose psでコンテナが作成されているか確認。
次にRailsプロジェクトのdatabase.ymlを編集します。config/database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password host: dbpasswordとhostの部分をdocker-compose.ymlの記述と合わせます。
$ docker-compose upコマンドでコンテナを立ち上げ、localhost:3000に接続し、RailsのHello World画面が表示されれば無事成功です!Vue.jsの導入
vue.jsをRailsで使用できるようにするためにまずはWebpackerの導入が必要。Gemfileに以下を追加します。
Gemfilegem 'webpacker'記述したら
$ docker-compose run web rails webpacker:installを実行自分の場合、Gemfileの記述を
gem 'webpacker', github: 'rails/webpacker'
とするとyarnエラーが出てインストールができませんでした。インストールが完了したらいろいろRailsのプロジェクトにファイルが追加されると思います。
次にVueをインストールしていきます。
$docker-compose run web rails webpacker:install:vueこれでVue.jsの導入が完了しました。
vue.jsファイルのビルド
次にVue.js関連のコンポーネントをJavascriptにコンパイルするため
$ docker-compose run web bin/webpackコマンドを実行します。RailsのViewsファイルに
<%=javascript_pack_tag 'hello_vue'%>と記述し問題なく表示されていれば成功です!参考
dockerでrails+vueの環境を作ったので解説
Rails5.2 + Docker環境にVue.js (Webpacker) を導入する
- 投稿日:2019-10-28T23:05:41+09:00
【jQuery】いい感じのアコーディオンを自作した
はじめに
アコーディオン(開閉メニュー)を実装するとき、jQuery標準APIの
slideUp()/slideDown()だと融通がきかなかったりするので簡単なスニペットを作りました。残念ながらjQuery依存です。そしていい感じかはわかりません。使い方
- jQueryとpublic/assets/js/app.jsを読み込む
- 対象要素に下記を指定する
class名 カスタム属性 js-accordion-target
→ 開閉する要素data-accordion-height
→ 初期表示の高さを指定(初期値: 0)js-accordion-trigger
→ クリックする要素data-accordion-target
→ ターゲットとなる要素を指定サンプルコード
html<!-- target --> <div class="js-accordion-target" id="target" data-accordion-height="50"> <p style="width: 300px;">Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum is simply dummy text of the printing and typesetting industry.</p> </div> <!-- trigger --> <a class="js-accordion-trigger" href="#" data-accordion-target="#target">read more</a> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <script src="app.js"></script>コードの解説
以下、Accordionクラスのコードです。
jsclass Accordion{ constructor() { this.targetClass = 'js-accordion-target'; this.$target = $('.' + this.targetClass); this.triggerClass = 'js-accordion-trigger'; this.$trigger = $('.' + this.triggerClass); this.dataTarget = 'data-accordion-target'; this.dataInitialHeight = 'data-accordion-height'; this.openClass = 'is-open'; this.ease = 'swing'; this.speed = 300; } /** * 初期化処理。下記の場合エラー * - トリガー要素に[data-accordion-target]属性が存在しない * - トリガー要素に指定した[data-accordion-target]要素が存在しない * - [data-accordion-target]属性を持った要素に.js-accordion-triggerが指定されていない */ init(){ try { if (!this.$target.length || !this.$trigger.length) return; this.$trigger.each((i, el) => { if ($(el).attr(this.dataTarget)) { if (!$($(el).attr(this.dataTarget)).length || !$($(el).attr(this.dataTarget)).hasClass(this.targetClass)) { throw new Error(this.targetClass + ' element does not exist'); } } else { throw new Error(this.dataTarget + ' attribute does not exist'); } }); $('[' + this.dataTarget + ']').each((i, el) => { if ($($(el).attr(this.dataTarget)).length && !$(el).hasClass(this.triggerClass)) { throw new Error(this.triggerClass + ' attribute does not exist'); } }); } catch (e) { console.error(e.message); return; } this.setTarget(); this.setEvent(); } /** * ターゲット要素の初期スタイル指定 */ setTarget(){ this.$target.each((i, el) => { let $el = $(el), initialHeight = $el.attr(this.dataInitialHeight); $el.css({ height: initialHeight ? Number(initialHeight) : 0, overflow: 'hidden', }) }); } /** * イベント定義 */ setEvent(){ $(window).on('resize', () => { this.resize(); }); this.$trigger.on('click', (e) => { this.click(e); return false; }); } /** * クリックイベント * @param {object} event クリックイベントオブジェクト */ click(event){ let $target = $($(event.currentTarget).attr(this.dataTarget)), initialHeight = $target.attr(this.dataInitialHeight), height = this.getHeight($target.contents()), h = 0; if ($target.hasClass(this.openClass)) { h = initialHeight ? Number(initialHeight) : 0; $target.removeClass(this.openClass); } else { h = height; $target.addClass(this.openClass); } this.setHeight({ $target: $target, speed: this.speed, height: h, }); } /** * リサイズイベント */ resize(){ this.$target.each((i, el) => { let $el = $(el), h = this.getHeight($el.contents()); if (!$el.hasClass(this.openClass)) return; this.setHeight({ $target: $el, speed: 0, height: h, }); }); } /** * ターゲット要素の高さ指定 * @param {object} param 対象要素・スピード・高さ */ setHeight(param){ let $target = param.$target, s = param.speed, h = param.height; $target.stop().animate({ height: h }, s, this.ease); } /** * 対象要素の高さを取得 * @param {object} $el 対象要素 * @return {number} 対象要素の高さ */ getHeight($el){ let h = 0; $el.each((i, el) => { if ($(el).get(0).nodeType === Node.TEXT_NODE) { $(el).wrap('<span>'); h += $(el).parent().outerHeight(true); $(el).unwrap('<span>'); } else { h += $(el).outerHeight(true); } }); return h; } } let accordion = new Accordion(); accordion.init()難しいことはしてないので、コメント読んでいただければなんとなく分かるかと思いますが、一応メソッドごとに解説します。
initメソッド
initメソッドでは初期化を行ってます。
まず、トリガー要素かターゲット要素が存在しない場合は、その後の処理が実行されないようreturnします。if (!this.$target.length || !this.$trigger.length) return;その後下記エラーをチェックし、該当すればエラーを投げます。
- トリガー要素に[data-accordion-target]属性が存在しない
- トリガー要素に指定した[data-accordion-target]要素が存在しない
- [data-accordion-target]属性を持った要素に.js-accordion-triggerが指定されていない
this.$trigger.each((i, el) => { if ($(el).attr(this.dataTarget)) { if (!$($(el).attr(this.dataTarget)).length || !$($(el).attr(this.dataTarget)).hasClass(this.targetClass)) { // トリガー要素に指定した[data-accordion-target]要素が存在しない throw new Error(this.targetClass + ' element does not exist'); } } else { // トリガー要素に[data-accordion-target]属性が存在しない throw new Error(this.dataTarget + ' attribute does not exist'); } }); $('[' + this.dataTarget + ']').each((i, el) => { if ($($(el).attr(this.dataTarget)).length && !$(el).hasClass(this.triggerClass)) { // [data-accordion-target]属性を持った要素に.js-accordion-triggerが指定されていない throw new Error(this.triggerClass + ' attribute does not exist'); } });エラーチェックを無事抜けたら、最後に
setTarget()とsetEvent()を呼び出します。try { // エラーチェック処理 } catch (e) { console.error(e.message); return; } this.setTarget(); this.setEvent();setTargetメソッド
setTargetメソッドでは、ターゲット要素(
.js-accordion-target)の初期スタイルを設定します。
height→ ターゲット要素が[data-accordion-height]属性を持っていたらその値、なければ0
overflow→ アコーディオンを閉じたとき子要素がはみ出さないようにoverflow: hiddenしてあげますthis.$target.each((i, el) => { let $el = $(el), initialHeight = $el.attr(this.dataInitialHeight); $el.css({ height: initialHeight ? Number(initialHeight) : 0, overflow: 'hidden', }) });setEventメソッド
setEventメソッドでは、下記のイベント定義を行います。
// トリガーのクリックイベント this.$trigger.on('click', (e) => { this.click(e); return false; });// リサイズイベント $(window).on('resize', () => { this.resize(); });clickメソッド
clickメソッドは、トリガー要素がクリックされた際に実行される処理です。
ざっくり以下のような処理を行ってます。
1. ターゲット要素が閉じたとき、開いたときの高さを取得
2. ターゲット要素の開閉状態を見て、値を設定
3. アニメーション// 1. ターゲット要素が閉じたとき、開いたときの高さを取得 let $target = $($(event.currentTarget).attr(this.dataTarget)), initialHeight = $target.attr(this.dataInitialHeight), height = this.getHeight($target.contents()), h = 0; // 2. ターゲット要素の開閉状態を見て、値を設定 if ($target.hasClass(this.openClass)) { h = initialHeight ? Number(initialHeight) : 0; $target.removeClass(this.openClass); } else { h = height; $target.addClass(this.openClass); } // 3. アニメーション this.setHeight({ $target: $target, speed: this.speed, height: h, });resizeメソッド
resizeメソッドは、リサイズ時にターゲット要素に対して行われる処理です。
リサイズ時に子要素の高さが変わるとレイアウトが崩れてしまうので、常にターゲット要素の高さを再計算してあげます。
流れとしては以下です。
1. ターゲット要素が開いたときの高さを取得
2. ターゲット要素が閉じてるときはreturn
3. 高さ調整resize(){ this.$target.each((i, el) => { // 1. ターゲット要素が開いたときの高さを取得 let $el = $(el), h = this.getHeight($el.contents()); // 2. ターゲット要素が閉じてるときはreturn if (!$el.hasClass(this.openClass)) return; // 3. 高さ調整 this.setHeight({ $target: $el, speed: 0, height: h, }); }); }setHeightメソッド
setHeightメソッドは、ターゲット要素の高さを設定します。
なお、引数で speed / height を渡せるようにしてるので、
resize()の高さ制御も、click()の開閉アニメーションもこの処理を実行してます。setHeight(param){ let $target = param.$target, s = param.speed, h = param.height; $target.stop().animate({ height: h }, s, this.ease); }getHeightメソッド
getHeightメソッドは、引数に渡ってきた要素の高さの合計値を返します。
ターゲット要素が開いたときの高さ(=子要素の高さの合計値)を取得するときに使用します。
なお、子要素がテキストノードの場合は高さが取れないので、一旦<span>でwrap → 高さ計算 →<span>をunwrap という荒業を行ってます。(絶対もっと良い方法ありますが)let h = 0; $el.each((i, el) => { if ($(el).get(0).nodeType === Node.TEXT_NODE) { $(el).wrap('<span>'); h += $(el).parent().outerHeight(true); $(el).unwrap('<span>'); } else { h += $(el).outerHeight(true); } }); return h;まとめ
大したものでは無いのですが、勉強がてらアコーディオン機能を作ってみました。
小さな機能に関しては自作してしまったほうが早かったり融通がきいたりするので、どんどん自作コードで身を固めていきたいです。
- 投稿日:2019-10-28T22:49:55+09:00
[ionic]3分クッキング ~ビンゴマシーン編~
はじめに
こんにちは!
Zizで活動しているkinetukiと申します。
今回は3分でビンゴマシーンを作っていきます。
宴会シーズンにぴったりの品ですね!用意するもの
- ionic4で作成したプロジェクト
- コーヒー(できればブラック)
- きのこの山 ※たけのこの里では代用不可なので気をつけてください
調理
html
まずはhtmlを調理していきます。
ビンゴの数字とスタート/ストップボタンを作りましょう。home.html<ion-content> <div class="div-main-display"> <ion-label>{{displayNum}}</ion-label> </div> <div class="ion-padding"> <ion-button *ngIf="!isRunning" expand="block" size="large" (click)="startSlot()"> スタート </ion-button> <ion-button *ngIf="isRunning" expand="block" size="large" color="medium" (click)="stopSlot()"> ストップ </ion-button> </div> </ion-content>scss
次に、先ほど作ったhtmlに下味を付けていきます。
素材の味を生かすために、シンプルな味付けにします。home.scss.div-main-display { height: 50vh; max-height: 50vh; width: 100vw; max-width: 100vw; text-align: center; font-size: 70vw; }tsファイル
最後にtsファイルを合わせて完成です。
home.tsexport class HomePage { bingoNums: Array<number> = Array.from({length: 75}, (_, i) => ++i); displayNum: number = 1; isRunning: Boolean = false; intervalId: any; constructor() {} startSlot() { if(this.bingoNums.length === 0){ return; } this.isRunning = true; //ドラムロール this.intervalId = setInterval(() => { this.displayNum = this.getBingoNum(this.bingoNums); }, 50); } stopSlot() { clearInterval(this.intervalId); //一度出た数字を削除 this.rmBingoNum(this.displayNum); this.isRunning = false; } getBingoNum(nums: Array<number>) { const firstIndex = 0; const lastIndex = nums.length - 1; const randomIndex = this.generateRandomIndex(firstIndex, lastIndex); return nums[randomIndex]; } generateRandomIndex(min: number, max: number) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; } rmBingoNum(num: number) { const arr = this.bingoNums; const rmIndex = arr.indexOf(num); if(rmIndex !== -1){ this.bingoNums.splice(rmIndex, 1); } } }完成品
こちらが完成品です。
いかがでしょうか?おわりに
ionicどころかフロントサイドを触り始めたばかりなので、
もっとこうしたほうがいいなどあればぜひ教えてくださると嬉しいです。
読んでいただきありがとうございました!
- 投稿日:2019-10-28T22:36:46+09:00
発火とは
はじめに
JavaScriptを習得した人・しようとしている人は、"発火"という単語を必ず聞いたことがあります。
そして、中にはこの言葉に馴染めず、学習が遅れてしまった人もいるでしょう。
本記事では、発火とはどういうものか、なんで発火というのか、について書かせて頂きます。※本記事は内容の正確さ・親切さに自身がないので、随時編集予定です。
内容
そもそも"発火"とはなんぞや?
発火とは、JavaScriptにおいてメソッドが実行されることをいいます。
JavaScriptでは、
①DOMの取得
②処理
③HTMLの操作
という順序でビューを変更しますが、この中で③がメソッドの発火によって行われます。どうやったら発火するの?
指定されたクラスに、指定されたイベントを起こすと発火します。
イベントを起こすというのは、例えばクリックする、送信するなどです。
クラスを指定するのは、DOMを取得することにより行えます。var btn = document.querySelector("button"); function Say() { console.log("Hello.world"); } btn.addEventListener("click", Say);例えば上のコードでは、
var btn = document.querySelector("button");
でDOMを取得し、btnに代入。(buttonを取得)
btn.addEventListener("click", Say);
で発火する条件を指定しています。
btn.addEventListener("click", Say);
btnでクラスの指定。
btn.addEventListener("click", Say);
でイベントを指定しています。
この場合、button(btn)をクリック(click)すると、関数Sayが実行される、
発火を使って言い換えると、button(btn)をクリック(click)すると、関数Sayが発火する
ことになります。なんでこんな呼び方に?
はっきりしませんが、一説には'イベントを実行する'という意味の
fire eventを訳したものだそうです。
これでだと訳した方も、原文の方も「もっと正確な言葉があったのでは?」と思ってしまうので、
もしかしたら違う理由があるのかもしれません。
- 投稿日:2019-10-28T22:31:18+09:00
Next.jsのDynamic Routing + Static HTML exportを組み合わせて使う
Next.js 9ではDynamic Routingが標準でサポートされ使用感が大きく改善されました。
このDynamic Routingで構築したアプリをexportした場合に一手間必要だったので考慮したことをまとめておきます。この構成でデプロイを行う際には、ビルド時にデータを全部仕込むか、クライアント側で(APIを叩くなどして)データを仕込むかで大きく対処方法が変わります。
今回の想定アプリ
次のようなページを持つアプリケーションを想定します。
/ /about /show/[id]1.ビルド時にデータを仕込む方法
exportする際には、
next.config.jsのexportPathMapを利用し、出力するファイルの調整を行っていきます。
exportPathMapで、キーにpath、バリューにpage(pageコンポーネントの位置)、query(クエリパラメータ)などの情報をもたせたオブジェクトをそれぞれ作り返り値とします。コードを見たほうが早いでしょう。const fetch = require('isomorphic-unfetch'); module.exports = { exportPathMap: async function() { const paths = { '/': { page: '/' }, '/about': { page: '/about' } }; const res = await fetch('https://api.tvmaze.com/search/shows?q=batman'); const data = await res.json(); const shows = data.map(entry => entry.show); shows.forEach(show => { paths[`/show/${show.id}`] = { page: '/show/[id]', query: { id: show.id } }; }); return paths; } };https://nextjs.org/learn/excel/static-html-export/exporting-other-pages より
この際、showのデータを参照するためにAPIを叩いて、結果からexportPathMapのデータを生成します。
2. クライアント側でデータを読み込む場合
ビルド後にもデータが増えることが想定されている場合、ビルド時にexportPathMapを使わない選択肢もあります。
exportPathMapを設定しない場合は、次のようなファイルが生成されます。/index.html /about/index.html /show/[id]/index.html問題は
/show/[id]/index.htmlです。ただ、NetlifyやFirebase Hostingにデプロイするだけでは、example.com/show/[id]でしか開くことができません。
そこで各プラットフォームに、example.com/show/1などのURLを叩いた時に、/show/[id]/index.htmlの内容を返すリライトの設定を行います。例えばNetlifyでは、公開ディレクトリに次のような
_redirectsを用意します。/show/:id /show/[id] 200同じくFirebase hostingではfirebase.jsonにリライトの設定を追加します。
こういった設定を加えることで、データが増えた場合でも404を返さずにコンテンツを表示できます。
ただ、この対応だけでは動的にogpを返却することや、速度的なデメリット(1の方法ではHTML読み込み時点でコンテンツが表示されている)を背負うことになります。
特に2.の方法は今後SSRを使いたいけど、今はSSRを行わず静的ファイルとして返す暫定対応的な使い方になると思います。
覚えて置くと初期フェーズからNext.jsの採用を行え、将来SSRをする際に移行コストが小さくなるのでおすすめです。
- 投稿日:2019-10-28T21:25:57+09:00
初心者によるプログラミング学習ログ 139日目
100日チャレンジの139日目
twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
139日目は、
おはようございます
— ぱぺまぺ@webエンジニアを目指したい社畜 (@yudapinokio) October 23, 2019
139日目
udemyでjavascript学習
疲れていたのあんまりすすまず#100DaysOfCode #javascript #駆け出しエンジニアとつながりたい #早起きチャレンジ
- 投稿日:2019-10-28T21:25:05+09:00
Ink Recognizer REST API を使用してデジタル インクの認識アプリを作成する
今回は比較的新しい Ink Recognizer を使用して、手書きで書いた文字を認識するアプリを作成してみようと思います。
Ink Recognizer をデプロイする
Azure Portal から Ink Recognizer をデプロイします。新しいリソースの作成で、「Ink」と入力すると出てくるので、任意の名前を付けてデプロイします。この記事を執筆時点ではまだプレビュー版でした。(2019/10/26時点)
デプロイが終わり構築されたリソースのページ移動すると、API に接続するための Key が表示されています。後程使用するのでこれをメモしておきます。
サンプルコードを GitHub よりデプロイする
Microsoft の GitHub にサンプルコートがあります。今回はそれを使用してアプリを作成します。
https://github.com/Azure-Samples/cognitive-services-REST-api-samples任意のローカルディレクトリに上記の GitHub コードをデプロイし、Ink Recognizer のアプリがあるディレクトリに移動します。
git clone https://github.com/Azure-Samples/cognitive-services-REST-api-samples cd SampleCode\cognitive-services-REST-api-samples\javascript\InkRecognition\javascript-app\src code .これを実行すると、Visual Studio Code が立ち上がります。
API Key を入力する
config.js の該当の箇所に最初にコピーしたサブスクリプションキーを入力します。
config.jsSERVER_ADDRESS = "https://api.cognitive.microsoft.com"; ENDPOINT_URL = SERVER_ADDRESS + "/inkrecognizer/v1.0-preview/recognize"; SUBSCRIPTION_KEY = "ここです!!!!"; // Languages for user to try LANGUAGE_TAGS_TO_TRY = ["en-US", "de-DE", "en-GB", "fr-FR", "hi-IN", "ja-JP", "ko-KR", "zh-CN"]; // Window.devicePixelRatio could change, e.g., when user drags the window to a display with different pixel density, // however, there is no callback or event available to detect the change. // In this sample, we assume devicePixelRatio doesn't change. PIXEL_RATIO = window.devicePixelRatio; MILLIMETER_PER_INCH = 25.4; PIXEL_PER_INCH = 96; MILLIMETER_TO_PIXELS = PIXEL_PER_INCH / (MILLIMETER_PER_INCH * PIXEL_RATIO); PIXEL_TO_MILLIMETERS = MILLIMETER_PER_INCH * PIXEL_RATIO / PIXEL_PER_INCH;サンプルコードの中にはあらかじめエンドポイントの URL がセットされていますので、そこは変更する必要はありません。
ここまでで一応 InkRecognizer のサンプルコードの設定が完了です。実際に動かしてみる
sample.html を任意のブラウザで開いてみましょう。開くと二つの空のボックスが表示されます。左側のボックスが字を書くところで、右側のボックスが InkRecognizer が認識した JSON を返すところです。
実際に「Hello」と手書きで書いてみました。右側のボックスには認識された JSON コードが返ってきていました。中身を見るとちゃんと「hello」と認識されていることが分かります。
さいごに
今回は初めて InkRecognizer を使用してみましたが、API のように非常に簡単に扱うことができるので、色々使い道はあるかなと思いました。次回の記事では日本語をどれくらい認知できるのかどうかについて探求しようと思います。
参考 URL
InkRecognizer Cognitive Service JavaScript Sample
https://github.com/Azure-Samples/cognitive-services-REST-api-samples/tree/master/javascript/InkRecognition/javascript-app
- 投稿日:2019-10-28T21:18:25+09:00
WebGL(GLSL)でカラースケール関数を実装する
カラースケール概要
データの値に応じて色を変えたい場合、下図のようなカラースケールを用いることがある。温度によって色分けされたサーモグラフィーの画面や、気温に応じて色分けされた地図などを思い浮かべれば良い。
カラースケールは大きく分けて以下の3種類に区分することができる。
- 1. Categorical
- 2. Diverging
- 3. Sequential
このうち、
Categoricalなカラースケールは単にRGBの配列を用意することで容易に実装できるが、残りのDiverging,SequentialはRGBの連続的な補間が必要になり、言語ごとに実装が必要になる。このような連続的カラースケールをWebGL(GLSL)で実装するのが本記事の内容。d3.jsの場合
JavaScriptでは、d3.jsの一部であるd3-scale-chromaticに全3パターンのカラースケールが実装されている。連続的なカラースケールの場合、0-1の範囲の任意の実数値を受けて色を返してくれるような関数になっている。
これらのソースコード(例えば冒頭の虹色スケール
d3.interpolateSpectral()に関するSpectral.js)を読んでみると、いくつか(10程度)のRGBの配列を直書きしたうえで、それらのRGBをSpline補間することで任意の実数値を受けられるようにしていることがわかる。
補間前のRGBの配列はd3.schemeSpectralのような形で取得することができるので、これを借りてきて補間処理を実装すれば、他の言語でもカラースケール関数が利用できることになる。WebGLのGLSLの場合はJavaScriptから値を渡すことができるので、d3から補間前の配列を直接渡せばOKということになる。GLSLでの実装
以下の手順によってGLSLで連続的なカラースケール関数を利用する
- 1. (JavaScriptで)d3-scale-chromaticからカラースケールの元となるRGB配列を取得する
- 2. 上記配列は
#ff0000のようなhex形式なので、0から1の間のRGBの実数値配列に変換する- 3. 上記RGBの配列を
uniformとしてJavaScriptからGLSLに渡す- 4. GLSLでBーspline補間を実装する
補間処理はd3-scale-chromaticのJavaScriptでの実装をGLSLに翻訳すれば良い。
補間処理とJavaScriptから渡されたカラースケールを用いて本記事冒頭のようなカラースケールを表示するフラグメントシェーダのコードは以下の通り。
このShaderを利用して実際に描画を行うJavaScriptコードはこちらを参照。
fragmentShader#define MAX_SCHEME_LENGTH 16 precision highp float; uniform float u_size; uniform int u_n; uniform vec3 u_colorScheme[MAX_SCHEME_LENGTH]; // Use this function because we cannot use dynamic value for array index in GLSL vec3 getData(float x) { for (int j=0; j<MAX_SCHEME_LENGTH; j++) { if (j == int(floor(x))) return u_colorScheme[j]; } return vec3(0.0, 0.0, 0.0); } // GLSL translation of basis() in d3 // https://github.com/d3/d3-interpolate/blob/master/src/basis.js vec3 basis(float t1, vec3 v0, vec3 v1, vec3 v2, vec3 v3) { float t2 = t1 * t1; float t3 = t2 * t1; return ((1.0 - 3.0 * t1 + 3.0 * t2 - t3) * v0 + (4.0 - 6.0 * t2 + 3.0 * t3) * v1 + (1.0 + 3.0 * t1 + 3.0 * t2 - 3.0 * t3) * v2 + t3 * v3) / 6.0; } // GLSL translation of exported function in the URL below // https://github.com/d3/d3-interpolate/blob/master/src/basis.js vec3 getColor(float t) { int n = u_n - 1; float i = t <= 0.0 ? 0.0 : (t >= 1.0 ? float(n) - 1.0 : floor(t * float(n))); float t2 = t <= 0.0 ? 0.0 : (t >= 1.0 ? 1.0 : t); vec3 v1 = getData(i); vec3 v2 = getData(i+1.0); vec3 v0 = i > 0.0 ? getData(i-1.0) : 2.0 * v1 - v2; vec3 v3 = i < float(n) - 1.0 ? getData(i+2.0) : 2.0 * v2 - v1; return basis((t2 - i / float(n)) * float(n), v0, v1, v2, v3); } void main() { // Fill pixels based on x coordinate vec2 pos = gl_FragCoord.xy / u_size; gl_FragColor = vec4(getColor(pos.x), 1.0); }以上
- 投稿日:2019-10-28T20:50:28+09:00
LIFF v2 で追加されたQRコードリーダーを使ってみる
はじめに
先日、LIFF v2がリリースされ、外部ブラウザ対応やQRコードリーダー等便利な機能が追加されました。
ということなので、QRコードリーダーを使ってみます。
できたもの
LIFF v2 で追加されたQRコードリーダーを試してみた
— Takuya Kanatani / 金谷拓哉 (@torisankanasan) October 28, 2019
読み取りはやw#LINE_API #LIFF pic.twitter.com/37oYJDzIuV以下の
友だち追加ボタンから、実際に試していただけます。手順
LIFFスターターアプリとして、サンプルが公開されています。
GitHubのline-liff-v2-starterリポジトリのREADME.mdに従って、操作していけばLIFFアプリのベースが作れます。あるいは @h-takauma さんの記事がわかりやすいので、参考にしてみてください。
LIFF v2でLINEログインを試してみた今回は、LIFFスターターアプリから使用しないコードを削除しました。
index.html<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>QRコードリーダー</title> </head> <body> <script src="https://static.line-scdn.net/liff/edge/2.1/sdk.js"></script> <script src="liff.js"></script> </body> </html>またワンクリックでQRコードリーダーの起動までいきたかったので、
liff.init()メソッドでLIFFアプリを初期化した後、liff.scanCode()メソッドでQRコードリーダーを呼び出しています。読み取った文字列を
liff.sendMessages()メソッドで、LIFFアプリが開かれているトーク画面にメッセージ送信し、liff.closeWindow()メソッドでLIFFアプリを閉じています。liff.jswindow.onload = function() { const defaultLiffId = "xxxxxxxxxxxxx"; initializeLiff(defaultLiffId); }; function initializeLiff(myLiffId) { liff .init({ liffId: myLiffId }) .then(() => { liff.scanCode().then(result => { const stringifiedResult = result.value; liff.sendMessages([{ 'type': 'text', 'text': stringifiedResult }]).then(() => { liff.closeWindow(); }).catch((error) => { window.alert('Error sending message: ' + error); }); }).catch(err => { window.alert('scanCode failed!'); }); }) .catch((err) => { window.alert('Something went wrong with LIFF initialization.'); }); }注意点は、LINE Developersコンソールで、LIFFアプリにScanQR権限を付与する必要があることと、
LIFFのサイズやエンドポイントURLを編集した際に、なぜかScanQR権限が勝手に
OFFに戻ってしまうので、ScanQR権限の状態は要注意ですw
(そのうち改善されるはず!!!)わかったこと
- QRの読み取りめっちゃはやい
- こちらのサイトで作成した、テキストを埋め込んだQRコードは読み取りできなかった。。
- ありきたりだけどQRコードスタンプラリーとか作れそう「LINEでQRスタンプラリー!!」
手軽にQRコードリーダーが使えるのはいいですね!
LIFF v2 ではQRコードリーダー以外の機能も追加されています。
@sumihiro3 さんの記事(LIFF v2 で追加されたAPIを試してみる)がわかりやすいので、参考にしてみてください。
- 投稿日:2019-10-28T19:30:59+09:00
GLSLスクール2019 第1回(2019.9.28)
WebGL総本山やWebGLの開発支援サイトwgld.orgを運営している、杉本 雅広さんによるGLSLの講義を受けてきました。(全4回)
遅ればせながら、今回は第1回のまとめ。周りを見渡すと、自分のようなひよっこエンジニアは見当たらず、オーラ全開ゴリゴリエンジニアといった感じで、「自分には早すぎたか…!?」と焦りました…;;
第1回は座学が多かったですが(むしろほぼ座学…?)内容としてはとても大事なことでした。受講内容は、基本的に事前に受講者に配布されていたサンプルを見ながら進めていくような感じなので、まとめノートにするのはなかなか難しいところがありました。
以下、講義のまとめノートです↓↓↓
GLSLとはなにかを知ろう 〜シェーダとGLSLの概念・基礎知識〜
進化するシェーダ
- シェーダは高い演算能力をもつGPUを使用するため、映像表現だけではなく様々な演算処理に使われるように。(ブラウザ上で大量の演算をする際、裏でWebGLに関連した技術を使ったりなど)
- とはいえ、メインはやはりグラフィックスの描画がほとんど。
3D APIとシェーダ
- 当たり前のことながら、3DCGを描画するためには大量の計算や工程が必要になる。そのため、大抵はGPUを使用する。
- GPUを直接触るのは難しいため、インターフェースとして3D APIを利用する。
- 3D APIで有名なところは、オープンソースの
OpenGLやWindowsに最適化されたDirectXなど。- 【 CPU (JavaScriptなど) 】 => 【 3D API 】 => 【 GPU 】
OpenGL と OpenGL ES
OpenGL ESはモバイル向けで軽量。OpenGL ES と WebGL
- WebGLはJavaScriptのAPI。内部的には
OpenGL ES 2.0とほぼ同じ。GLSL
OpenGL Shading Languageの略。- OpenGLファミリーで利用できるシェーダ記述言語。
- 現在のWebGL(WebGL 1.0)で利用するGLSLは
GLSL ES 1.0。(GLSL ES 1.0は、OpenGL ES 2.0と利用されているものと同じ)固定機能パイプラインとプログラマブルシェーダ
- かつてのOpenGLには、現在のシェーダの機能がすべて備わっており、すべてOpenGL単体で完結するように作られていた。(他の3D APIも同様)
- それでは汎用性に乏しく、ある程度同じことしかできなかったため、GLSLのような自由にシェーディングを制御できる機構が生まれた。
固定機能パイプライン:OpenGL内に組み込まれていた、現在のシェーダに相当する機能。プログラマブルシェーダ:自由にシェーディングを制御できる機構。- WebGLやOpenGL ESには
固定機能パイプラインが存在しないため、プログラマブルシェーダを利用する必要がある。WebGLで利用できるシェーダ
頂点シェーダ
- 頂点の座標変換が主な役割。
- 3Dプログラミングでは頂点によってあらゆるものが表現される。
- 頂点が1つで点、頂点が2つで線、頂点が3つで三角形…
頂点の個数 = 頂点シェーダが実行される回数フラグメントシェーダ
- ピクセルシェーダとも呼ばれる。
- 最終的にスクリーンに描画されるピクセルひとつひとつを処理する。
- 頂点によって描画されることになるピクセルひとつひとつがフラグメントシェーダの処理対象になる。
画面の解像度の大きさ = 実行されるピクセルの個数その他のシェーダ
- 代表的なものでは、ジオメトリシェーダ。(WebGLでは使えない)
- プラットフォームを限定するものもある。
- シェーダの中でも、
頂点シェーダとフラグメントシェーダは最も基本的なシェーダであり、非常に重要。グラフィックスパイプライン
- ディスプレイに映像が映し出されるまでは、主にGPU側ではざっくりと以下のようになっている。
- 【 3Dモデルデータ (配列など) 】 => 【 頂点シェーダ (座標変換など) 】 => 【 ビューポート変換 】 => 【 ラスタライズ 】 => 【 フラグメントシェーダ (着色など) 】 => 【 ブレンディングなど 】 => 【 ディスプレイ 】
- この処理の流れをグラフィックスパイプラインと呼ぶ。(レンダリングパイプラインと呼ぶ場合も)
- かつては固定機能シェーダが埋め込まれており固定されていた(
固定機能パイプライン)。頂点情報などを流し込むとレンダリングまで自動的に実行されていた。- グラフィックスパイプラインのいくつかの工程を自由に実装できるようにしたものが
プログラマブルシェーダ。- 自由に実装できるといっても、処理の順序はある程度決まっている。(=シェーダが実行される順序も決まっている)
- 頂点シェーダがどう描画すべきかを確定させ、処理すべきピクセルに対してフラグメントシェーダが実行される。
- 上記から、常に
フラグメントシェーダより先に頂点シェーダが呼び出される。サンプルを見ながら考えていこう
- ここから、事前に受講者に配布されていたサンプル001のコードを見ながら、WebGL側の処理の流れを把握する。
WebGLの初期化の流れ
- とにかく工程が多い!(それはどの3D APIでも同じ)
GPUで動作するGLSLとCPU側のロジックが連携するために必要な手順を確認していく。- CPU側であるJavaScriptやC++が、OpenGLやWebGLなどの3D APIをインターフェースとして、GPUで描画処理をおこなう。
- JavaScript側での処理についての流れ
- canvas要素からWebGL描画用のコンテキストを取得する。これを通して様々な処理をおこなう。
- GLSLで書かれたシェーダのソースコードを取得してコンパイルする。
- GLSLのソースコードは
単なる文字列でJavaScript側でコンパイルするため、極論、変数に代入した文字列でもよいし、外部ファイルにするならば拡張子はなんでもよい。- シェーダはコンパイルされることで、単なる文字列から、実態のある
シェーダオブジェクトになる。- 頂点シェーダとフラグメントシェーダという異なるシェーダを、それぞれデータの受け渡しができるようにするため
プログラムオブジェクトにリンクさせる。- この
プログラムオブジェクトは、シェーダ同士やシェーダとCPUとやりとりをおこなってくれる、いわば管制塔のようなもの。- これでシェーダを使った描画をおこなうための準備が完了する。
WebGLの描画までの流れ
プログラムオブジェクトを通してGLSL側の変数の情報(変数名やデータ型など)をアプリケーション側(JavaScript側)で取得する。- CPUとGPUが物理的に離れている関係上、対象の変数がGPU内のメモリの中のどの
ロケーションを示しているかを取得する必要がある。ロケーションはプログラムオブジェクトを通して取得することができる。これでCPU側とGPU側がつながったことになる。- JavaScriptが動作するのはCPU側であり、シェーダが動作するのはGPU側であるため、GPU側の管制塔である
プログラムオブジェクトが橋渡し役となるイメージ。- CPUからは
ロケーションを指定してデータを流し込む。GLSLの変数
- GLSLの変数宣言の際に指定するもの
- 変数名
- 変数のデータ型
- int(整数)、float(浮動小数点)、bool(真偽値)、vec系(ベクトル。vec2〜vec4。中身はfloat)、mat系(行列。mat2〜mat4。中身はfloat)など。
- 変数の種類をあらわす修飾子(ストレージ修飾子)
attribute:頂点の情報が格納される変数に用いられる。それぞれの頂点が固有に持っている座標位置や色などの情報。(頂点に固有の情報を一般に「頂点属性」という)uniform:アプリケーション(JavaScript)から渡されるグローバル変数。どの頂点に対しても均一に作用する。全体に影響するパラメータなどを制御できる。varying:頂点シェーダからフラグメントシェーダの値の受け渡しに使われる。シェーダ間のやり取り用。ロケーション取得方法の基本
attribute修飾子とuniform修飾子を持つ変数は、変数名とまったく同じ文字列をプログラムオブジェクト経由で検索することで、変数のロケーションを取得できる。- 描画が開始されるまでの間に事前にシェーダにデータを送るため、
ロケーションをあらかじめ取得しておく必要がある。- 取得した
ロケーションの使い方は、attribute変数とuniform変数で手順が若干異なる。frag.glslprecision mediump float; uniform vec4 globalColor; void main() { gl_FragColor = globalColor; }javascript// GLSL側の変数 globalColor のロケーションを取得している。 // gl:WebGLコンテキスト、program:プログラムオブジェクト gl.getUniformLocation(program, 'globalColor');ロケーション取得から描画まで
ロケーションの取得までできたら、残るはデータをシェーダに送り込んで描画をおこなうだけ。- 手順はざっくりと以下のようになっている。
頂点の情報を定義
- 頂点は単純に配列として定義すればよい。GLSL側でvec3なら3個1セット、vec2なら2個1セット、floatなら単体の値。
- 以下の例は、GLSL側で
vec3で宣言されたattribute変数 positionを渡すデータのため、JavaScript側でも、xyzでデータがワンセットになるように要素数を調整する。vert.glslattribute vec3 position; void main(){ gl_Position = vec4(position, 1.0); gl_PointSize = 16.0; }javascript// 頂点座標の定義(シェーダ内で参照する attribute変数の元になる最初のデータ定義) position = [ 0.0, 0.0, 0.0, // 1つ目の頂点の xyz -0.5, 0.5, 0.0, // 2つ目の頂点の xyz 0.5, 0.5, 0.0, // 3つ目の頂点の xyz -0.5, -0.5, 0.0, // 4つ目の頂点の xyz 0.5, -0.5, 0.0, // 5つ目の頂点の xyz ];背景をクリアする色の指定
- WebGLやGLSLではRGBAは0.0〜1.0の範囲で指定する。
- GLSLでは
vec4(1.0, 1.0, 1.0, 1.0)が完全に不透明な白(rgba(255, 255, 255, 1))に値する。- JavaScript側で背景をクリアする色の指定をおこなうのも同様の値の範囲で指定する。
ビューポートが描画される矩形の定義
- WebGLやOpenGLでは、描画する領域をビューポートと呼ぶ。
- ウィンドウサイズやレンダリングの対象となっているcanvas要素の大きさが変化するときには、ビューポートの再設定ももちろん必要になる。(canvas要素の大きさに合わせるのが良さそう)
- ビューポートの再設定をしないと、canvas要素とビューポートのサイズに差が生じ、表示位置がずれて見えたり大きさが違って見えてしまう。
どのプログラムオブジェクトを利用するかしっかり指定
- うっかり別の
プログラムオブジェクトに変えてしまうとシェーダがそっくり入れ替わることになる。- 複数のシェーダを同時に切り替えながら使う場合は、描画前にしっかりと対象となる
プログラムオブジェクトを選択しなくてはならない。GPUにデータを送り、描画命令を発行する
- WebGLやOpenGLでは、描画を実際におこなうのはGPUであるため、描画をおこなうために必要なデータは、事前にGPUに渡しておく必要がある。
- データを送ってから描画命令を発行すると、その時点でGPUに送られている情報を使ってレンダリングされる。
VBOとattributeロケーションを使って頂点を有効にする =>uniformロケーションを使って、uniform変数にデータを転送する => 転送済みの情報を使って、頂点を画面にレンダリングする
VBOとは、Vertex Berffer Objectの頭文字をとったもの。シェーダに渡す値を詰める専用オブジェクトのようなもの。GLSL側で gl_ の接頭語が付いているものはビルトイン変数
ビルトイン変数:定義しなくてもそのまま利用可能な変数。- GLSLで定義されている
ビルトイン変数には、gl_の接頭語がついている。以下は一例。- 頂点シェーダ
gl_Position:頂点シェーダから出力する頂点情報。gl_PointSize:頂点を点として描画するときのサイズ。(ピクセル単位)- フラグメントシェーダ
gl_FragColor:フラグメントシェーダから出力する色情報。(RGBAのvec4)頂点属性に頂点カラーを追加する(サンプル002)
- 頂点カラーを頂点シェーダで受け取り、varying変数を使って、頂点の色をフラグメントシェーダへ渡す。
uniform変数とattribute変数とvarying変数の違いをしっかり意識する。javascript// 〜 中略 〜 // 頂点座標の定義 position = [ 0.0, 0.0, 0.0, -0.5, 0.5, 0.0, 0.5, 0.5, 0.0, -0.5, -0.5, 0.0, 0.5, -0.5, 0.0, ]; // 頂点カラーの定義(頂点シェーダの変数colorはvec4だから4つずつ) color = [ 1.0, 1.0, 1.0, 1.0, // 1つ目の頂点の色 1.0, 0.0, 0.0, 1.0, // 2つ目の頂点の色 0.0, 1.0, 0.0, 1.0, // 3つ目の頂点の色 0.0, 0.0, 1.0, 1.0, // 4つ目の頂点の色 0.5, 0.5, 0.5, 1.0, // 5つ目の頂点の色 ]; // 〜 中略 〜 // グローバルカラー gColor = [1.0, 1.0, 1.0, 1.0]; // 〜 中略 〜vert.glslattribute vec3 position; attribute vec4 color; // 頂点カラー varying vec4 vColor; // varying変数でフラグメントシェーダへ値を送る void main(){ // フラグメントシェーダへ送る値を、varying変数へ代入しておく vColor = color; gl_Position = vec4(position, 1.0); gl_PointSize = 16.0; }frag.glslprecision mediump float; uniform vec4 globalColor; varying vec4 vColor; // varying変数間で変数名は同一でなければならない void main(){ gl_FragColor = globalColor * vColor; }頂点にポイントサイズの頂点属性を追加する(サンプル003)
- floatの頂点属性なので、ストライドが1になる。
- 頂点を描画する形式をプリミティブと呼ぶ。
- サンプルで使われている
gl.POINTSのほか、gl.LINESやgl.TRIANGLESなど。javascript// 〜 中略 〜 // 頂点座標の定義 position = [ 0.0, 0.0, 0.0, -0.5, 0.5, 0.0, 0.5, 0.5, 0.0, -0.5, -0.5, 0.0, 0.5, -0.5, 0.0, ]; // 頂点カラーの定義 color = [ 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.5, 0.5, 0.5, 1.0, ]; // 頂点のポイントサイズの定義 size = [2.0, 4.0, 8.0, 16.0, 32.0]; // 〜 中略 〜 // グローバルカラー gColor = [1.0, 1.0, 1.0, 1.0]; // 〜 中略 〜 // 転送済みの情報を使って、頂点を画面にレンダリングする // gl:WebGLコンテキスト、position:頂点座標(配列データ、glslではvec3で定義) gl.drawArrays(gl.POINTS, 0, position.length / 3); // gl.POINTS:点(矩形)として描く // 〜 中略 〜vert.glslattribute vec3 position; attribute vec4 color; attribute float size; // ポイントサイズの頂点属性を追加 varying vec4 vColor; void main(){ vColor = color; gl_Position = vec4(position, 1.0); gl_PointSize = size; }frag.glslprecision mediump float; uniform vec4 globalColor; varying vec4 vColor; void main(){ gl_FragColor = globalColor * vColor; }カーソルに連動して動くサンプルを作ってみる(サンプル004)
- カーソルの動きに合わせてすべての頂点が一律に動くので、uniform変数を利用する。
- カーソルの位置は
vec2として扱う。javascript// 〜 中略 〜 // マウスカーソルが動いたことを検出するためのイベントを記述 mouseX = 0; mouseY = 0; window.addEventListener('mousemove', (e) => { let x = e.clientX; let y = e.clientY; let width = window.innerWidth; let height = window.innerHeight; x = (x - width / 2.0) / (width / 2.0); y = (y - height / 2.0) / (height / 2.0); mouseX = x; mouseY = -y; }); // 頂点座標の定義 position = [ 0.0, 0.0, 0.0, -0.5, 0.5, 0.0, 0.5, 0.5, 0.0, -0.5, -0.5, 0.0, 0.5, -0.5, 0.0, ]; // 頂点カラーの定義 color = [ 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.5, 0.5, 0.5, 1.0, ]; // 頂点のポイントサイズの定義 size = [2.0, 4.0, 8.0, 16.0, 32.0]; // 〜 中略 〜 // グローバルカラー gColor = [1.0, 1.0, 1.0, 1.0]; // 〜 中略 〜vert.glslattribute vec3 position; attribute vec4 color; attribute float size; uniform vec2 mouse; // マウスカーソルの位置(正規化済み) varying vec4 vColor; void main(){ vColor = color; // マウスカーソルの動きを頂点座標にそのまま加算する gl_Position = vec4(position + vec3(mouse, 0.0), 1.0); gl_PointSize = size; }frag.glslprecision mediump float; uniform vec4 globalColor; varying vec4 vColor; void main(){ gl_FragColor = globalColor * vColor; }ウィンドウサイズに応じた変形(サンプル005)
gl_Positionに出力する頂点の情報は常に-1.0〜1.0の範囲で扱われる。(正規化デバイス座標)- ウィンドウサイズに応じて歪まないようにするためには、
-1.0〜1.0の空間に収まるよう頂点を変換する。javascript// 〜 中略 〜 // マウスカーソルが動いたことを検出するためのイベントを記述 mouseX = 0; mouseY = 0; window.addEventListener('mousemove', (e) => { let x = e.clientX; let y = e.clientY; let width = window.innerWidth; let height = window.innerHeight; x = (x - width / 2.0) / (width / 2.0); y = (y - height / 2.0) / (height / 2.0); mouseX = x; mouseY = -y; }); // 頂点座標の定義 position = [ 0.0, 0.0, 0.0, -0.5, 0.5, 0.0, 0.5, 0.5, 0.0, -0.5, -0.5, 0.0, 0.5, -0.5, 0.0, ]; // 頂点カラーの定義 color = [ 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.5, 0.5, 0.5, 1.0, ]; // 頂点のポイントサイズの定義 size = [2.0, 4.0, 8.0, 16.0, 32.0]; // 〜 中略 〜 // グローバルカラー gColor = [1.0, 1.0, 1.0, 1.0]; // 〜 中略 〜vert.glslattribute vec3 position; attribute vec4 color; attribute float size; uniform vec2 mouse; uniform vec2 resolution; // 解像度 varying vec4 vColor; void main(){ vColor = color; // 解像度の情報からアスペクト比を計算して頂点を変換する // 例)横2:縦1のaspectは0.5 float aspect = 1.0 / (resolution.x / resolution.y); vec3 p = position * vec3(aspect, 1.0, 1.0); gl_Position = vec4(p + vec3(mouse, 0.0), 1.0); gl_PointSize = size; }frag.glslprecision mediump float; uniform vec4 globalColor; varying vec4 vColor; void main(){ gl_FragColor = globalColor * vColor; }やってみよう 水平に並べる(サンプル006)
- 水平に左から右まで等間隔に頂点を並べる。
vert.glslattribute vec3 position; attribute float ratio; // 取りうる値は、0.0 ~ 1.0 void main(){ // ratio を -1.0 〜 1.0 に変換して、X座標に入れればよい float x = ratio * 2.0 - 1.0; gl_Position = vec4(position + vec3(x, 0.0, 0.0), 1.0); gl_PointSize = 8.0; }やってみよう サイン波で揺らす(サンプル007)
- 水平に並べた後、サイン波で揺らしてみる。
- uniform変数を使って時間の経過(秒単位)が送られてくる。
vert.glslattribute vec3 position; attribute float ratio; // 0.0 ~ 1.0 uniform float time; // 経過時間 void main(){ float x = ratio * 2.0 - 1.0; float y = sin(x + time) * 0.5; // 0.5 => 波の高さの調整 gl_Position = vec4(position + vec3(x, y, 0.0), 1.0); gl_PointSize = 8.0; }やってみよう 円形に配置する(サンプル008)
- サインとコサインを組み合わせて円を作る。
vert.glslattribute vec3 position; attribute float ratio; // 0.0 ~ 1.0 uniform float time; // 経過時間 const float PI = 3.1415926; // 円周率 void main(){ float radian = ratio * 2.0 * PI; float x = cos(radian); float y = sin(radian); gl_Position = vec4(position + vec3(x, y, 0.0), 1.0); gl_PointSize = 8.0; }最後に
誤字・脱字などありましたら、すみません…
- 投稿日:2019-10-28T19:22:02+09:00
asyncな無名関数を使って非同期処理を書く
モダンなjsではasyncFunctionで同期っぽく非同期処理を書きたいわけですが、
何でもかんでもawaitで止めて直列処理にしてしまうのが不適切な場合もあります。例えば、以下のようなケースです。
- 非同期処理Bは画面を機能させるために必須の処理である。
- 非同期処理Bは非同期処理Aの結果に依存する。
- 非同期処理C及びDは必要な処理ではあるが、画面初期描画時に必須ではない
- 非同期処理Dは非同期処理Cの結果に依存する
この場合、画面を初期描画するのにBが必須なため、AとBに関してはasync functionの中で
const aResult = await a(); const bResult = await b(aResult); //bResultを基に画面が描画されると書けばOKです。
では、ここでCとDをどう書くか?
const aResult = await a(); const bResult = await b(aResult); const cResult = await c(); const dResult = await d(cResult); //bResultを基に画面が描画されるという風にとにかくawaitでつなげても動きはします。
が、こう書くと本来はaやbの待ち時間にc,dの処理を始められないため非効率的です。というわけで、効率化を考えると、以下のようにc,d部分は以下のように書く必要があります。
c() .then(cResult => d(cResult)) .then(dResult => {//dResultを使った処理}); const aResult = await a(); const bResult = await b(aResult);cとdに依存性がなければ
c(); d(); const aResult = await a(); const bResult = await b(aResult);と書いて終わりですが、cとdは直列化しないといけない場合は、thenでつなぐ必要があります。
が、せっかくasyncなのにここでthenが出てくるのはちょっと格好悪い気がします。無名async関数
(async() => { const cResult = await c(); const dResult = await d(cResult); //dResultを使った処理 })(); const aResult = await a(); const bResult = await b(aResult);asyncは無名関数でも問題なく利用でき、即時実行すればPromiseを返してくれます。
処理が長かったり、他でも使う場合は関数として切り出した方が良いと思いますが、
他で使う予定がない処理の場合ささっと無名関数で書いてしまうのもアリだと思います。
- 投稿日:2019-10-28T19:21:05+09:00
JavaScriptの配列操作に役立つ13のヒントとトリック
以下はDuomly ( Webサイト / Twitter )による記事、13 useful JavaScript array tips and tricks you should knowの日本語訳です。
Duomlyはプログラミング学習コースを提供しているWebサイトです。
ただランディングページにたいしたことが書いてないので、詳細はよくわかりません。
ここはもう少し書いておいてほしいところ。13 useful JavaScript array tips and tricks you should know
配列はJavaScriptで最も一般的な概念のひとつであり、内部に格納されたデータを操作する手段は多数存在します。
配列はJavaScriptの最も基本的なトピックであり、プログラミング学習パスの最初に学習するものだという前提のうえで、この記事ではあなたが知らないかもしれない、しかしコーディングにおいてはとても役立つかもしれない、幾つかのトリックを紹介します。
さっそく始めましょう。1. Remove duplicates from an array
配列から重複した値を削除する方法は、JavaScriptの質問の中でも非常によく見かけるものです。
ここでは、新しめの機能であるSet()を使った高速で簡単な解決策を紹介します。
これには2種類、.from()を使う方法、およびスプレッド構文...を使う方法があります。var fruits = ["banana", "apple", "orange", "watermelon", "apple", "orange", "grape", "apple"]; // fromを使う方法 var uniqueFruits = Array.from(new Set(fruits)); console.log(uniqueFruits); // ["banana", "apple", "orange", "watermelon", "grape"] // ...を使う方法 var uniqueFruits2 = [...new Set(fruits)]; console.log(uniqueFruits2); // ["banana", "apple", "orange", "watermelon", "grape"]簡単ですね。
2. Replace the specific value in an array
コーディング中に、配列の一部の値だけを書き換えなければならない事態に陥ることはよくあります。
これを実現するとても短い記法があるのですが、もしかしたらご存じないかもしれません。
このために.spliceを使うことができます。
これは3つの引数を取り、ひとつめが変更する開始位置のインデックス、ふたつめが変更する数、そして3つめ以降は存在すればかわりに新しい値を入れ込みます。var fruits = ["banana", "apple", "orange", "watermelon", "apple", "orange", "grape", "apple"]; fruits.splice(0, 2, "potato", "tomato"); console.log(fruits); // ["potato", "tomato", "orange", "watermelon", "apple", "orange", "grape", "apple"]3. Map array without .map()
全員が
.map()を使いこなしているだろうと思いますが、.map()を使わずに同じ効果を得られる全く別のソリューションが存在します。
それは.from()メソッドです。var friends = [ { name: 'John', age: 22 }, { name: 'Peter', age: 23 }, { name: 'Mark', age: 24 }, { name: 'Maria', age: 22 }, { name: 'Monica', age: 21 }, { name: 'Martha', age: 19 }, ] var friendsNames = Array.from(friends, ({name}) => name); console.log(friendsNames); // ["John", "Peter", "Mark", "Maria", "Monica", "Martha"]4. Empty an array
要素が大量に入った配列があるのですが、何らかの理由で全ての要素を削除したいとします。
要素をひとつひとつ削除したくないですか?
それを行うことは、わずか1行でできます。
lengthを0にすると中身が全部消えます。var fruits = ["banana", "apple", "orange", "watermelon", "apple", "orange", "grape", "apple"]; fruits.length = 0; console.log(fruits); // []5. Convert array to an object
今手元にひとつの配列がありますが、諸事情により同じデータを持つオブジェクトが必要になりました。
配列をオブジェクトに変換する最も手っ取り早い方法は、スプレッド構文を使用することです。var fruits = ["banana", "apple", "orange", "watermelon"]; var fruitsObj = { ...fruits }; console.log(fruitsObj); // {0: "banana", 1: "apple", 2: "orange", 3: "watermelon", 4: "apple", 5: "orange", 6: "grape", 7: "apple"}6. Fulfill array with data
いちから配列を作成して何らかのデータで埋めたい場合、あるいは同じ値の並んだ配列が必要な場合、
.fill()メソッドがその機能を持っています。var newArray = new Array(10).fill("1"); console.log(newArray); // ["1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1"]7. Merge arrays
.concat()を使わずに複数の配列をひとつにマージする方法を知っていますか?
その手段がここにはあります。
薄々お察しかとは思いますが、スプレッド構文...は配列操作したいときに非常に便利であり、今回も同じです。var fruits = ["apple", "banana", "orange"]; var meat = ["poultry", "beef", "fish"]; var vegetables = ["potato", "tomato", "cucumber"]; var food = [...fruits, ...meat, ...vegetables]; console.log(food); // ["apple", "banana", "orange", "poultry", "beef", "fish", "potato", "tomato", "cucumber"]8. Find the intersection of two arrays
ふたつの配列から共通項を抽出する処理は、JavaScriptの面接において直面する可能性の高い課題のひとつです。
あなたが配列のメソッドを理解しているか、ロジックを構築できるかの能力を見ることができるからです。
ここまでに紹介したメソッドのひとつを使うことで、ふたつの配列の共通部分を見つけることができます。
またチェックしている配列の値が重複していないことを確認するためには.filterと.includesメソッドを使用します。var numOne = [0, 2, 4, 6, 8, 8]; var numTwo = [1, 2, 3, 4, 5, 6]; var duplicatedValues = [...new Set(numOne)].filter(item => numTwo.includes(item)); console.log(duplicatedValues); // [2, 4, 6]9. Remove falsy values from an array
まず"falseっぽい値"をはっきりさせましょう。
JavaScriptにおいて"falseっぽい値"とは、false、0、''、null、NaN、undefinedです。
これでfalseっぽい値を配列から削除する方法がわかりました。
それは.filter()メソッドです。var mixedArr = [0, "blue", "", NaN, 9, true, undefined, "white", false]; var trueArr = mixedArr.filter(Boolean); console.log(trueArr); // returns ["blue", 9, true, "white"]10. Get random value form the array
配列からランダムに値を選択しなければならない状況に追い込まれることが人生にはよくあります。
簡単高速で短くクリーンなコードを保つには、配列長からランダムなインデックス番号を生成するとよいでしょう。var colors = ["blue", "white", "green", "navy", "pink", "purple", "orange", "yellow", "black", "brown"]; var randomColor = colors[(Math.floor(Math.random() * (colors.length)))]11. Reversing an array
配列を逆さにしたい場合、複雑なループと関数を使って新たな配列を作る必要はありません。
最初から専用のメソッドが用意されているので、1行で終わらせることができます。var colors = ["blue", "white", "green", "navy", "pink", "purple", "orange", "yellow", "black", "brown"]; var reversedColors = colors.reverse(); console.log(reversedColors); // ["brown", "black", "yellow", "orange", "purple", "pink", "navy", "green", "white", "blue"]12. .lastIndexOf() method
JavaScriptには、指定された要素が最後に出現する位置のインデックスを返すという興味深いメソッドが存在します。
たとえば配列内に重複した値が存在する場合、それが最後に出現する位置を見つけることができます。var nums = [1, 5, 2, 6, 3, 5, 2, 3, 6, 5, 2, 7]; var lastIndex = nums.lastIndexOf(5); console.log(lastIndex); // 913. Sum all the values in the array
面接で頻繁に聞かれる、もうひとつの質問です。
特に難しいことはなく、.reduceメソッドひとつで解決することができます。var nums = [1, 5, 2, 6]; var sum = nums.reduce((x, y) => x + y); console.log(sum); // 14Conclusion
この記事では、ちょっとしたコーディングに役立ち、コードを短く簡潔に保つための13のヒントとトリックを紹介しました。
配列だけではなくそれ以外のデータ型にも、調べてみる価値のある様々な裏技があることを忘れないでください。
この記事で紹介したソリューションをあなたが気に入って、開発プロセスの改善に役立つことを願っています。Have a nice coding!
コメント欄
「Awesom!!」「Thanks!」
「length=0知らんかった、すごい!」「Array.fromの第二引数知らなかった、勉強になる。」「
Array.map()のかわりに常にArray.from()を用いなければならない、というのはよくわからない。」「“”は"に修正すべき。」「そのとおり、記事にコードを含めるときは有効なコードであることを確認しよう。」
「3番目は配列っぽいオブジェクト、NodeListとかに使うとより便利だよね。」
「8番目、
Array.prototype.includesは線形でSet.prototype.hasは定数だから、大きい配列比較にはvar duplicatedValues = […new Set(numOne)].filter(item => numTwo.includes(item));のかわりにlet firstValues = [...new Set(numOne)];let duplicatedValues = numTwo.filter(item => firstValues.has(item));ってしたほうがいい」「firstValuesはhas持ってないから動かないよ。」「ほんとだ間違えた、正しくはlet firstValues = new Set(numOne);だった。」「11番目、
reverse()はcolors自身も変更してしまう。変更したくないときは[...colors].reverse();ってしよう。」「colors.slice().reverse()でもいいぞ。」感想
元記事は
“”や…が全角で入っていたので、全て半角に修正しています。
英語圏の人が手動で…とか入れたり、さらに全角ダブルクォートの開きと閉じを使い分けるとは考えにくいですし、そもそも構文エラーなので、何らかの自動変換ライブラリが誤爆しているのだと思われます。
なかなか不思議なライブラリもあったものですね。あと
varももう流行らないのでletとかにしておいてほしいですね。とはいえ細かい(パースエラーは細かくないが)点を除けば、配列を実用的に操作したいときに役立つトピックだと思います。
自分自身をさくさく書き換えているので何か言いたい人も出てきそうですが、そんなものより目の前で動くコードが必要なんですよ。補足
Array.spliceは、3番目以降の引数があれば削除位置に挿入される。
3個以上あっても全て挿入され、その後のインデックスが全部ずれる。
3番目以降の引数がなければ単に削除される。var fruits = ["banana", "apple", "orange", "watermelon", "apple", "orange", "grape", "apple"]; fruits.splice(0, 2, "a", "b", "c", "d"); console.log(fruits); // ["a", "b", "c", "d", "orange", "watermelon", "apple", "orange", "grape", "apple"] fruits.splice(0, 9); console.log(fruits); // ["apple"]配列要素を全削除するには、変な参照とかでもないかぎり、単純に上書きするほうが適切だろう。
結果が同じとはいえ、length=0は直感的操作とはあまり思えない。var fruits = ["banana", "apple", "orange", "watermelon", "apple", "orange", "grape", "apple"]; fruits = []; console.log(fruits); // []
false、0、''、null、NaN、undefinedのうち、NaNだけ==falseがfalseになる。
NaNは自分自身も含めたあらゆる値との比較がfalseになるので仕様なのだがちょっとわかりにくい。
- 投稿日:2019-10-28T19:11:38+09:00
ESLint v6.6.0
前 v6.5.0 | 次 (2019/11/23 JST)
ESLint v6.6.0 has been released: https://t.co/oHo5t86A0k
— ESLint (@geteslint) October 25, 2019ESLint
6.6.0がリリースされました。
小さな機能追加とバグ修正が行われています。これまで ESLint は隔週リリースを行ってきましたが、アクティブなメンテナが減少していることから、今後は月間リリースとなります。
質問やバグ報告等ありましたら、お気軽にこちらまでお寄せください。
? 本体への機能追加
context.getCwd()各ルールから現在のディレクトリを取得できるようになりました。
例module.exports = { meta: {}, create(context) { console.log(context.getCwd()) //→ /home/a-user/projects/foo return {} }, }この値は基本的に
process.cwd()と同じですが、いくつかの場合に異なる値になります。
CLIEngineオブジェクトを初期化した後にprocess.chdir()関数によって CWD を変更した場合CLIEngineのコンストラクタ オプションとして独自の CWD を指定した場合また、ブラウザ上で実行している場合など、CWD が不明な場合は
undefinedを返します。? 新しいルール
特になし。
? オプションが追加されたルール
no-param-reassign
ignorePropertyModificationsForRegex? #11275
指定した正規表現にマッチする名前の引数について、プロパティの変更を許可するオプションが追加されました。
例/*eslint no-param-reassign: [error, { props: true, ignorePropertyModificationsForRegex: ["^req"] }]*/ //✘BAD function f1(req, res) { res.value = 0 } //✔GOOD function f2(req, res) { req.value = 0 }use-isnan
enforceForIndexOf? #12379
浮動小数点数の仕様により、
NaNとの比較は常にfalseになります。これを報告するuse-isnanルールに、Array#indexOfメソッドも報告するオプションが追加されました。代わりに、Array#findIndexメソッドを利用したり、NaNを特別に処理する新しいメソッドArray#includesを利用したりしましょう。例/*eslint use-isnan: [error, { enforceForIndexOf: true }]*/ //✘BAD var indexOfNaN= myArray.indexOf(NaN) var hasNaN = myArray.indexOf(NaN) >= 0 //✔GOOD var indexOfNaN = myArray.findIndex(Number.isNaN) var hasNaN = myArray.includes(NaN)no-unsafe-negation
enforceForOrderingRelations? #12414
比較演算子の左辺をオペランドとする否定演算子を警告するオプションが追加されました。
例/*eslint no-unsafe-negation: [error, { enforceForOrderingRelations: true }] */ //✘BAD if (! a < b) {} //✔GOOD if (!(a < b)) {}no-extra-parens
enforceForNewInMemberExpressions? #12436
new式に直接プロパティアクセスを繋げているときに、そのnew式を囲む括弧を無視するためのオプションが追加されました。例/*eslint no-extra-parens: [error, all, { enforceForNewInMemberExpressions: false }] */ //✔GOOD var obj = (new Foo()).prop;✒️
eslint --fixをサポートしたルール特になし。
⚠️ 非推奨になったルール
特になし。
- 投稿日:2019-10-28T18:22:56+09:00
export default しているものをasを使ってimportしたい
都合でdefaultでexportしている名称と同じ名称の変数などを定義したい場合、
適当な名前で適当に回避していましたがちゃんと対応できました(勉強不足がすぎる)。
下記のようなmoduleをimportしたい場合、
import先で同じ名前の変数があったりすると重複してしまい困ったことになりますがimportしたいmoduleexport default class TodoStore extends VuexModule implements TodoList {}
{ default as XXX }とするとエイリアスをつけてimportができます。import todoStore from '@/store/todo' import {default as todo} from '@/store/todo' // todoStoreをtodoというaliasでimport const todoStore = foo
- 投稿日:2019-10-28T17:19:47+09:00
Laravel で flatpickr を使ってカレンダーでの月選択
普通にflatpickrをインストールして、日付を選択するだけなら容易なのですが、
flatpickrの公式から月のみ表示して、月を選択できるようになったのでやってみます。ミスがありましたらご指摘お願いいたします。
動作
公式より
こちらから確認できますインストール
CDN版
//flatpickrのCDN <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css"> <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script> //flatpickrの月選択プラグイン <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr@latest/dist/plugins/monthSelect/style.css"> <script src="https://cdn.jsdelivr.net/npm/flatpickr@latest/dist/plugins/monthSelect/index.js"></script>npm
npmnpm i flatpickr --saveresources/js/app.jsimport flatpickr from "flatpickr"; import monthSelectPlugin from 'flatpickr/dist/plugins/monthSelect/index';resources/sass/app.js@import "../../node_modules/flatpickr/dist/themes/airbnb.css"; @import "../../node_modules/flatpickr/dist/plugins/monthSelect/style.css";使用
インストールしたら使用できるようになるので、使ってみます
resources/js/app.jsflatpickr(".js-calendar", { plugins: [ new monthSelectPlugin({}) ], })コンパイルして表示を確認します
npm run dev参考にしたサイト
- 投稿日:2019-10-28T16:48:40+09:00
【Vue.js】Composition APIを利用したコンポーネントの作成方法について
Composition APIとは
従来のdata, computedを用いたAPIによる問題を改善するための、Vue.jsの新たなコンポーネント作成方法です。
https://vue-composition-api-rfc.netlify.com/#summaryVue3.xで使用可能になるとのことですが、以下のプラグインを使用することで、Vue2.xでも利用可能です。
https://github.com/vuejs/composition-apiComposition APIを利用したコンポーネントの例
<template> <div> <p>{{ reactiveValue }}</p> <p>{{ computedValue }}</p> <div> <h1>hoge</h1> <p>{{ hogeMethod() }}</p> <p>{{ hogeValue }}</p> </div> <div> <h1>fuga</h1> <p>{{ fugaMethod() }}</p> <p>{{ fugaValue }}</p> </div> </div> </template> <script lang="ts"> import { createComponent, computed, reactive, onMounted } from '@vue/composition-api' function hoge() { const hogeValue = 'hoge' const hogeMethod = () => { return 'hogeMethod' } return { hogeValue, hogeMethod } } function fuga() { const fugaValue = 'fuga' const fugaMethod = () => { return 'fugaMethod' } return { fugaValue, fugaMethod } } export default createComponent({ props: { value: { type: String, default: '' } }, setup(props, context) { onMounted(() => { console.log('onMounted') }) const reactiveValue = reactive('reactive value') const computedValue = computed(() => props.value) const hogeValue = hoge() const fugaValue = fuga() return { computedValue, reactiveValue, ...hogeValue, ...fugaValue } } }) </script>Composition APIを利用してコンポーネントを実装する
ここからは
@vue/composition-apiをベースにComposition APIを見ます。setup()
これまでプロパティに記載していた
data,computed,mountedは全てsetup()の中に内包することになります。
また、templateで使用する値は全てsetupで返却する必要があります。import { createComponent, computed, reactive, onMounted } from '@vue/composition-api' setup(props, context) { // ライフサイクルメソッドもsetup内で実装する onMounted(() => { console.log('onMounted') }) const reactiveValue = reactive('reactive value') const computedValue = computed(() => props.value) const hogeValue = hoge() const fugaValue = fuga() return { computedValue, reactiveValue, ...hogeValue, ...fugaValue } }setupのメリット
Composition APIを使用することによって、データを機能単位にまとめられるというメリットがあります。
従来のAPIでは、1つのコンポーネントが有する機能Aに対して、
data,computedなど、プロパティ単位で分割されていました。そのコンポーネントが更に機能B、Cを有する場合、機能に対するデータがあちこちに分散することで、管理が煩雑になってしまいます。data() { return { 機能Aのデータ, 機能Bのデータ, 機能Cのデータ } }, computed() { 機能Aのデータ() {...}, 機能Bのデータ() {...}, 機能Cのデータ() {...} }, methods: { ...これをComposition APIに置換することで、機能毎にデータをまとめることが出来ます
setup() { // 機能Aに対する実装 const A = () => {...} // 機能Bに対する実装 const B = () => {...} // 機能Cに対する実装 const C = () => {...} return { A, B, C } }setupの分割
setupにほとんどの機能を実装するということで、setupの肥大化が懸念されますが、こちらは、機能毎にfunctionを切ることで、肥大化を防止出来ます。
createComponent外で、functionを作成します。それぞれのfunctionで、templateで使用したい値を返却するのを忘れてはいけません。
最終的には、functionの返り値をそのままsetupの返り値にしてあげます。import { createComponent, computed, reactive, onMounted } from '@vue/composition-api' function hoge() { const hogeValue = 'hoge' const hogeMethod = () => { return 'hogeMethod' } return { hogeValue, hogeMethod } } function fuga() { const fugaValue = 'fuga' const fugaMethod = () => { return 'fugaMethod' } return { fugaValue, fugaMethod } } export default createComponent({ props: { value: { type: String, default: '' } }, setup(props, context) { onMounted(() => { console.log('onMounted') }) const reactiveValue = reactive('reactive value') const computedValue = computed(() => props.value) const hogeValue = hoge() const fugaValue = fuga() return { computedValue, reactiveValue, ...hogeValue, ...fugaValue } } })注意点として、機能分離した際の名前の衝突です。
上記例におけるhoge()とfuga()に同様のメソッド名がある場合、setupで最後に返却した方に上書きされます。その際には警告などが出力されないため、知らず知らずのうちに上書きが発生する。ということも考えられます。そのため、機能分離のためのルールを設定するなど、多少人力が必要になるかもしれません。
最後に
Composition APIを用いることで、肥大化したコンポーネントで、1機能の修正のために
dataとmethodsをスクロールで往復する状況が改善されるかもしれません。
(そもそも、そのようなコンポーネントは再設計した方がいいかもしれませんが...)以上です。ありがとうございました。
参考記事
https://qiita.com/ryo2132/items/f055679e9974dbc3f977
https://vue-composition-api-rfc.netlify.com/#api-introduction参考にさせていただきました。ありがとうございました。
- 投稿日:2019-10-28T16:30:51+09:00
HTMLレンダリングエンジン
目次
- ブラウザとは(ブラウザコンポーネント)
- ブラウザとサーバーの通信
- HTMLレンダリングエンジン
- JavaScriptエンジン
- 日本ブラウザシェア率(2016~)
ブラウザとは
ウェブブラウザ(インターネットブラウザ、web browser)とは、 World Wide Web (WWW) の利用に供するブラウザであり、ユーザエージェント (UA) である。具体的には、ウェブページを画面や印刷機に出力したり、ハイパーリンクをたどったりするなどの機能がある。単にブラウザ(ブラウザー)と呼んだ場合、多くはウェブブラウザのことを指す。
ブラウザコンポーネント
各コンポーネント
- ユーザインターフェース
- ユーザは操作できる部分:アドレスバー、戻る/進むボタン、メニューなど
- ブラウザ エンジン
- ユーザインターフェースとレンダリングエンジンの間の情報の受け渡しを担当
- ⭐️HTMLレンダリングエンジン
- HTMLとCSSを解析し、画面に表示
- ⭐️JavaScriptエンジン
- JavaScriptを動かしを担当
- ネットワーキング
- HTTPリクエストなどのネットワーク呼び出しを担当
- UIバックエンド
- チェックボックスやウィンドウなどのコアウィジェットの描画を担当
- データストレージ
- Cookieなどのデータの保存を担当
ブラウザとサーバーの通信
- アドレスバーにアクセス先を入力
- ネットワーク経由でサーバーへリクエスト
- リクエスト内容より処理してHTMLを生成
- CSSとJSファイルなどもクライアントへ返信
- レンダリングエンジンとJavaScriptエンジンより画面を表示
HTMLレンダリングエンジン
HTMLレンダリングエンジンとは、ウェブページ記述用言語で書かれたデータを解釈し、実際に画面に表示する文字や画像などの配置を計算するプログラムである。HTMLに加え、CSS、XMLなどを解釈できるものが多い。
主なHTMLレンダリングエンジン
Trident (Internet Explorer、QQ浏览器) - トライデント EdgeHTML (Microsoft Edge) - エッジ Gecko (Firefox) - ゲッコー Servo - サーボ KHTML (Konqueror) WebKit (Safari) - ウェブキット Blink (Chromium, Chrome, Opera, Microsoft Edge) - ブリンク
HTMLレンダリングエンジンの歴史
参照:Browser engine From Wikipedia, the free encyclopedia
補足: EdgeブラウザのChromiumベース移行、マイクロソフトが正式発表
JavaScriptエンジン
JavaScriptエンジンとはJavaScriptコードを実行するプログラムまたはインタプリタのことです。
JavaScriptエンジンの種類
ブラウザ レンダリングエンジン JavaScriptエンジン IE Trident Chakra (Jscript9) Microsoft Edge EdgeHTML Chakara (JavaScript) Firefox Gecko SpiderMonkey Konqueror KHTML KJS Safari WebKit JavaScriptCore Chrome, Opera Blink V8 参照:PhantomJSと各ブラウザのJavascriptエンジンまとめ
参照:V8エンジンでのJavaScriptの機能と最適化コードの書き方に関する5つのベストプラクティス
日本ブラウザシェア率(2016~)
補足:Desktop、Mobile、Tabletを含む
[出典:https://gs.statcounter.com]
ご静聴ありがとうございます
- 投稿日:2019-10-28T15:58:11+09:00
【初心者】営業から転職のために【備忘録7】
10/25~27で勉強したこと
JavaScript入門!
- 基本的に【console.log("文字列");】で出力をする。
- //コメント で出力しない文章を書くこともできる。
- + - * /で四則計算。%で割り算の余りを出力できる。
変数
- 同じ文章を出力する際や、同じ値を繰り返し使用する際に使う。 変更にも対応しやすく、値の意味もわかりやすくなるため変数は使用される。
- 変数は上書き(ex)や追加(ex2)などができる。上書きする際は【let】は不要
【ex:let number=2; number=3;】【ex2:let number=2; number +=2;】定数
- const number=〇;で定数をして出力が可能。
- 長いコードになればなるほど途中で代入の数字が変わってしまっているなどの心配が出てくる。
そこで定数のconstを使用していると、途中の数値上書きが不可なので定数を使ったほうが安心。- 定数の場合文章連結に+を使うのではなくて別の連結方法がある。 console.log(
私の名前は${name}です);振り返り・気を付けること
- console[ .]log ドットを忘れてしまうことがあったので気を付ける。
- 数字を代入する際は""は必要ない!気を付けること。
- number +=2; の+と=の間にスペースを入れるとエラーが出たので気を付けること!
- 定数での${}で連結する際は `` を文頭文末に入れ忘れないように気を付ける。
- 投稿日:2019-10-28T15:31:57+09:00
awaitできるsetTimeoutを1行で書く方法
awaitすることができるsetTimeoutを1行で書くイディオムです。await new Promise(resolve => setTimeout(resolve, 3000)) // 3秒待つ // ... 3秒後の処理 ...
- 投稿日:2019-10-28T15:13:10+09:00
web入門(3)
はじめに
JavaScriptとは、webサイト上で、htmlとcssでは表現できない機能を実装する言語です。
よく、jsって略されます。Javaという言語と名前は似てますが、全然違うので、気をつけてください。また、jQueryとは、JavaScriptをより扱いやすくしたフレームワークのことです。フォルダー構成
デスクトップ上に、js-pracというフォルダーを作り、その中でindex.htmlとmain.jsを作る。
/Users/(ユーザー名)/Desktop/js-prac |-index.html (表示用) |-main.js (機能面)index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <title>JavaScriptの練習</title> </head> <body> <h1>Hello World</h1> </body> </html>JavaScript(jQuery)を使う
html上に直接JavaScriptを書く方法もありますが、今回は別ファイルに分けて読み取る方法を採用します。
また、jQueryを使用する方法は、ダウンロードをする方法とCDNを用いる方法がありますが、今回はCDNを用いる方法を採用します。
CDN(Content Delivery Network)とは簡単に言うと負荷を肩代わりしてくれるレンタルサーバのようなものです。下図のように一部の負荷を肩代わりしてもらうおかげで自分のサーバの負荷が下がり、安定してサービスを提供できるようになります。
では、index.htmlを以下のように修正します。
index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <title>JavaScriptの練習</title> </head> <body> <h1>Hello World</h1> <!-- CDNの読み込み --> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" ></script> <!-- main.jsの読み込み --> <script src="./main.js"></script> </body> </html>これで、jQueryを使う準備は終わりです。では、実際にjQueryを使ってみましょう。
まず、index.htmlのh1にid属性をつけます。
index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <title>JavaScriptの練習</title> </head> <body> <!-- idを追加する --> <h1 id="hello">Hello World</h1> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" ></script> <script src="./main.js"></script> </body> </html>次に、main.jsを編集していきます。
main.js$(function() { $("#hello").click(function() { $(this).text("こんにちは、世界"); }); });
$("#hello")で、htmlで指定したidを取得し、click()でクリックした時どうするか、function() {$(this).text("こんにちは、世界");}で、id="hello"を指定した要素のテキストを 「こんにちは、世界 」 に置き換えてるという流れです。chromeで開いて確認してみましょう。Hello World をクリックすると、こんにちは、世界 が表示されることが確認できます。
大まかに説明すると以下のような形になります。上の例では、idを取得していましたが、class,タグ(a, h2 など)もcssと同様に取得することが可能です。
$("#idの名前").jQueryのイベント(function() { // メソッド });$(".classの名前").jQueryのイベント(function() { // メソッド });$("htmlのタグの名前").jQueryのイベント(function() { // メソッド });それでは、よく使うパターンをまとめておきます。
1. クリックしたら、フェードアウトする
main.js$(function() { $("#hello").click(function() { $(this).fadeOut(); }); });2. ホバーしたら、色が変わる
main.js$(function() { $("#hello").hover(function() { $(this).css("color", "red"); }); });3. ホバーしたら、色が変わり、離したら色が元に戻る
main.js$(function() { $("#hello").on({ 'mouseenter' : function(){ $(this).css("color", "red") }, 'mouseleave' : function(){ $(this).css("color", "#000") } }); });4. ボタンを押したら、数字が足されていく
index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <title>JavaScriptの練習</title> </head> <body> <h1 id="hello">Hello World</h1> <!-- 以下を追加 --> <input id="txt" type="text" value="0" readonly /> <input id="btn" type="button" value="add!" /> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" ></script> <script src="./main.js"></script> </body> </html>main.js$(function() { $("#hello").click(function() { $(this).text("こんにちは、世界"); }); $("#btn").click(function() { let value = $("#txt").val(); // id="txt"のvalue属性を取得して、変数valueに代入 value = parseInt(value); // 変数valueの型をstring型->int型にキャスト $("#txt").val(value + 1); // id="txt"のvalue属性にvalue+1を入れる }); });5. ドロップダウンメニューの実装
index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <title>JavaScriptの練習</title> </head> <body> <h1 id="hello">Hello World</h1> <!-- 以下を追加 --> <input id="btn" type="button" value="表示する" /> <div id="menu" style="background-color: #ddd; height: 200px; padding-top: 20px; display: none" > <p>ドロップダウンメニューです。</p> </div> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" ></script> <script src="./main.js"></script> </body> </html>main.js$(function() { $("#hello").click(function() { $(this).text("こんにちは、世界"); }); $("#btn").click(function() { if ($(this).val() == "表示する") { $(this).val("非表示にする"); $("#menu").slideDown(1000); // 引数に秒数指定(単位:ミリ秒) } else { $(this).val("表示する"); $("#menu").slideUp(1000); } }); });
- 投稿日:2019-10-28T13:07:14+09:00
JavaScriptでURLからOGP取得する
JavaScriptでURLからOGP取得します。
結論
const url = "https://qiita.com/ksyunnnn/items/bfe2b9c568e97bb6b494"; fetch(url).then(res => res.text()).then(text => { const el = new DOMParser().parseFromString(text, "text/html") const headEls = (el.head.children) Array.from(headEls).map(v => { const prop = v.getAttribute('property') if (!prop) return; console.log(prop, v.getAttribute("content")) }) })結果
og:title JavaScriptでURLからOGP取得する - Qiita og:type article og:url https://qiita.com/ksyunnnn/items/bfe2b9c568e97bb6b494 og:image https://cdn.qiita.com/assets/qiita-fb-2887e7b4aad86fd8c25cea84846f2236.png # 以下略デベロッパーコンソールとかで適当に試してみてください
- 投稿日:2019-10-28T12:26:00+09:00
Array.prototype.fill()にオブジェクトをそのまま渡してハマった
知らなくてハマったのでこれから知らなくてハマる人のために書きます。
顛末
最初は単純な二次元配列でやっていたところを状態持たせようとオブジェクトに変更してハマりました。お馬鹿さんですね。
const fillArray = arr => { // before // fillにそのままオブジェクトを渡してはダメです return arr.map(x => new Array(x.length).fill({char: 'ほ', blacklist: []})) } const maze = fillArray(generateEmptyArray([5,10])) /* ほほほほほ ほほほほほ ほほほほほ ほほほほほ ほほほほほ ほほほほほ ほほほほほ ほほほほほ ほほほほほ ほほほほほ charだけ抜き出すとこう */ maze[0][0]['char'] = '黒' /* 黒黒黒黒黒 ほほほほほ ーー略ーー ほほほほほ ほほほほほ え? */わけがわからない。
解決
const fillArray = arr => { // after // fillしたあとにmapで埋めてあげるといい感じ return arr.map(x => new Array(x.length).fill().map( x => { return {char: 'ほ', blacklist: []} })) } const maze = fillArray(generateEmptyArray([5,10])) maze[0][0]['char'] = '黒' /* 黒ほほほほ ほほほほほ ーー略ーー ほほほほほ ほほほほほ ヨシ! */解決したのでテキストを埋め込む再帰関数に投げます。
fillGrid(maze, [...'黒猫列車はお客様第一']) /* 黒ほほほほ 猫ほほほほ 列ほほほほ 車客様ほほ はお第一ほ ほほほほほ ほほほほほ ほほほほほ ほほほほほ ほほほほほ にゃあん! */ありがとうございました。
補遺
よく考えたらArray.prototype.fill()自体使わなくていいのでは?
const generateFilledArray = (x, y) => { return [...Array(y)].map(_=>[...Array(x)].map(_=>({char: 'ほ', blacklist: []}))) } fillGrid(generateFilledArray(10,10), [...'銀河を繋ぐ黒猫列車、本日ひとりめのお客様がご乗車ですっ']) /* 銀河ぐ黒猫列車ほほほ ほを繋ほ日本、ほほほ ほほほほひとりめのほ ほほほほ車乗ほ客おほ ほほほほでごが様ほほ ほほほっすほほほほほ ほほほほほほほほほほ ほほほほほほほほほほ ほほほほほほほほほほ ほほほほほほほほほほ いいでしょう! */参考
Array.prototype.fill() with object passes reference and not new instance - Stack Overflow
エンジニアのハマり時間とその技術的難易度の相関関係
- 投稿日:2019-10-28T10:54:35+09:00
【JavaScript入門】ES2015の概要
参考書籍 WEB+DB PRESS Vol.87 Software Design 2019年3月号 改訂新版JavaScript本格入門 初めてのJavaScript ES2015について ECMAScript ES6やES2015のESと言うのはECMAScriptの略 ECMAScriptはEcma Internationalによって定められるJavaScriptの標準規格/言語仕様 JavaScriptはECMAScriptの仕様に基づいた言語のひとつ 標準化 どのようなプログラムを書いたらどのように動くのかを言語の仕様としてきちんと定義すること ブラウザごとに動作が違う問題を、どの処理系でも動作を同じにすることで解決することが目的 ES2015で追加された構文 文字列のテンプレートリテラル let・constキーワードによる変数宣言(ブロックスコープ) 関数の引数のデフォルトパラメータ 関数の可変長引数 アロー関数 ジェネレータ関数(停止や再開ができる特殊な関数) classキーワードによるクラス宣言 importとexportによるモジュール構文 他にもArrayなどの既存のオブジェクトに対して様々な関数が追加され、機能が強化された ES2015に追加されたオブジェクト Promise: 非同期処理を抽象化するデザインパターン Symbol: ユニークな値を表現する新たなプリミティブ型 Reflect / Proxy: オブジェクトの操作をカスタマイズするしくみ Set / WeakSet: 一意なデータスタックを表すオブジェクト Map / WeakMap: Key-Valueのデータ構造を表すオブジェクト その他関連項目 トランスパイラ:新しいJavaScriptの記法を古いJavaScriptに変換するアプリケーション Polyfill:古いブラウザに対して新しい機能の代替的な実装を追加するスクリプト ES6ポリフィルとは、ES6の新しい組込みクラスやメソッドを古い環境でも使えるようにJavaScriptで実装したライブラリ。ポリフィルで実現できることは限られるが、ビルドが不要なためトランスパイラより手軽に導入できる。 テンプレート文字列 テンプレート文字列を利用することで、以下のような文字列表現が可能になる。 文字列への変数の埋め込み 複数行にまたがる(=改行文字を含んだ)文字列 script.js let name = '鈴木'; // テンプレート文字列では、「\`」(バッククォート)で文字列をくくる let str = `こんにちは、${name}さん。 今日も良い天気ですね!`; //改行文字を、テンプレート文字列ではそのまま表現 console.log(str); 定数 ES2015で標準化された(以前は一部のブラウザーによる拡張仕様) 単語をアンダースコアで区切るのが一般的 定数を利用することで値の意味が明確になり、コードの可読性が増す 修正箇所が一箇所になり、修正漏れを未然に防ぐことができる script.js const TAX = 1.08; var price = 100; console.log(price * TAX); // 108
- 投稿日:2019-10-28T09:30:22+09:00
Nuxt.jsプロジェクトにDart Sassを導入する
Nuxt.jsプロジェクトにDart Sassを導入した時の知見。
- Node.js v10.16.3
- Nuxt.js v2.10.2
- sass(Dart Sass) v1.23.1
- create-nuxt-appなどでNuxt.jsプロジェクトを構築済み
- scssを使用
Dart Sassとは
Dart Sassとは、Googleが開発するDartというプログラミング言語で作成された、Sassのコンパイラです。DartはJavaScriptにコンパイル可能なため、node-sassと同じようにnpm経由でインストールできます。少しわかりにくいですが、sassというパッケージ名でnpmに公開されています。
また、Dart SassにはSassの新しい機能が先行実装される予定があったり、Node.jsのバージョン依存問題も解消できるなどのメリットがあるので、今後のSassコンパイラのスタンダードになることも考えられます。
Nuxt.jsプロジェクトにDart Sassを導入する方法
Dart Sassを導入する前に、create-nuxt-appなどでNuxt.jsプロジェクトを作成しましょう。
Nuxt.jsでDart Sassを使うためにはsass(Dart Sass)とsass-loaderが必要です。さらに、コンパイル速度を向上させるためにfibersのインストールも推奨されているので、次のようにyarnやnpmでパッケージをインストールします。
yarn add -D sass sass-loader fibers次に、nuxt.config.jsにsass-loaderの設定を追記します。node-sassの場合は特に何も設定せずに使用できましたが、Dart Sassを使用する場合は、現状追加で設定が必要なようです。
Dart Sassに関連する部分だけ抜粋すると次のようになります。
// nuxt.config.js const Sass = require('sass') const Fiber = require('fibers') export default { ... build: { loaders: { scss: { implementation: Sass, sassOptions: { fiber: Fiber } } } } }これでインストールと設定は終わりです。
.vueファイルにDart Sassを書くときは、node-sassと同じように、<style>要素にlang="scss"属性を付与するだけです。<template> <p>Hello World!</p> </template> <script> export default { name: 'HelloWorld' } </script> <style lang="scss"> p { color: tomato; } </style>
- 投稿日:2019-10-28T09:30:22+09:00
Nuxt.jsプロジェクトでDart Sassを使う
Nuxt.jsプロジェクトにDart Sassを導入した時の知見。
- Node.js v10.16.3
- Nuxt.js v2.10.2
- sass(Dart Sass) v1.23.1
- create-nuxt-appなどでNuxt.jsプロジェクトを構築済み
- scssを使用
Dart Sassとは
Dart Sassとは、Googleが開発するDartというプログラミング言語で作成された、Sassのコンパイラです。DartはJavaScriptにコンパイル可能なため、node-sassと同じようにnpm経由でインストールできます。少しわかりにくいですが、sassというパッケージ名でnpmに公開されています。
また、Dart SassにはSassの新しい機能が先行実装される予定があったり、Node.jsのバージョン依存問題も解消できるなどのメリットがあるので、今後のSassコンパイラのスタンダードになることも考えられます。
Nuxt.jsプロジェクトにDart Sassを導入する方法
Dart Sassを導入する前に、create-nuxt-appなどでNuxt.jsプロジェクトを作成しましょう。
Nuxt.jsでDart Sassを使うためにはsass(Dart Sass)とsass-loaderが必要です。さらに、コンパイル速度を向上させるためにfibersのインストールも推奨されているので、次のようにyarnやnpmでパッケージをインストールします。
yarn add -D sass sass-loader fibers次に、nuxt.config.jsにsass-loaderの設定を追記します。node-sassの場合は特に何も設定せずに使用できましたが、Dart Sassを使用する場合は、現状追加で設定が必要なようです。
Dart Sassに関連する部分だけ抜粋すると次のようになります。
// nuxt.config.js const Sass = require('sass') const Fiber = require('fibers') export default { ... build: { loaders: { scss: { implementation: Sass, sassOptions: { fiber: Fiber } } } } }これでインストールと設定は終わりです。
.vueファイルにDart Sassを書くときは、node-sassと同じように、<style>要素にlang="scss"属性を付与するだけです。<template> <p>Hello World!</p> </template> <script> export default { name: 'HelloWorld' } </script> <style lang="scss"> p { color: tomato; } </style>
- 投稿日:2019-10-28T06:17:35+09:00
React入門 -なぜReactを使うのか-
はじめに
私がReactに出会い1年程度経過し、React自体にも大きな変化がありました。今回は、Reactの良さを知ってもらいたいと思い、筆を取りました。
また、改めて自分の中のReactの知識を整理しようというのがサブ目標です。何回かに分けて1つのアプリを作れる程度までを記事にまとめていきたいと思ってます。筆者のステータスは以下の通りです
- React・Next.jsを使って簡単なアプリを作ったことがある
- アルバイトでSpringを使ったwebアプリの開発をしている(フロントはjQueryゴリゴリ)
Reactに出会って使用するに至った経緯
概略: jQueryで地獄を見たためReactに救いを求めました。
前述の通りjQueryを良く使用していて基本は問題なかったのですが、画面項目が増え動きがリッチになるにつれ実装難易度やメンテナンス性に限界を感じてきました。
そこでたまたま出会ったのがReactでした。しかし、調べてみても「MVCのVのみを担当するフレームワークで ──」「仮装DOMが──」などと説明されていても「は?」って感じで、イマイチどういったモノなのか掴めずにいました。
しかし実際に使ってみると、実にシンプルなフレームワーク/ライブラリであり、悩みを解決してくれる存在であることがわかりました。元々JSが好きだったのもありとても馴染みやすかったです。Reactとは
公式ドキュメントには以下のように紹介されてます。
A JavaScript library for building user interfaces
ユーザインターフェース構築のための JavaScript ライブラリこれだけではわからないので、主な機能の説明と実装例を用いて紹介していきたいを思います。
Reactを使うと何がいいのか?
こちらも公式ドキュメントのトップから抜粋
- Declarative (宣言的なView)
- Component-Based (コンポーネントベース)
もう1つ「Learn Once, Write Anywhere (一度学習すれば、どこでも使える)」ということが挙げられていますが、これは一旦スルーで。
Declarative (宣言的なView)
まず「宣言的」って何?というとこですね。
Reactで実装する際に私たちが実装するのは「ある状態において何が表示されるべきか」です。
「この値をここに表示して」、「この場合はこっちを表示して」というのをただただ宣言していけば良いわけです。そしてその宣言通りに効率良くUIの更新(DOMの操作)をする必要があります。
そこで登場するのが仮想DOMです。仮想DOMは通常のDOMと同じツリー構造をとります。
仮想DOMが構築し直されると、差分のみが実際のDOMに反映される、といった具合です。仮想DOMの構築はサーバーサイドレンダリング(SSR)でテンプレートエンジンを使ってHTMLを生成するのに良く似ています。SSRでの描画で画面の値が不整合になったなんていう記憶はないので、それに近い方法を取れる仮想DOMを使わない手はありません。
加えて、必要な箇所のDOMの更新しか行われないため、下手なピュアのJSよりもパフォーマンスが向上します。Component-Based (コンポーネントベース)
ReactではUIの状態、見た目、動きをコンポーネントという単位で分割します。単純な入力欄からページまで全てをコンポーネントとみなします。コンポーネントを組みわせていくことでUIを構築していきます。
分割することで再利用が可能になり、全体的な見た目の統一感を生みます。コンポーネントは自身の状態を管理し、見た目と振る舞いを定義します。そのため状態をDOMに置くことがなくなります。DOMにある値はどこからでも変更可能ですが、コンポーネントの状態はアクセスできるスコープが限られるので状態の遷移が追いやすくなります。
Reactを書いてみる
九九を表示するアプリを考える
- 何の段を表示させるかを入力する
- 入力された段の九九の表を表示する
jQueryの場合
Reactで書く前に比較対象としてjQueryを使った場合の実装をのせておきます
See the Pen kuku_jQuery by nabekou (@nabekou29) on CodePen.
data属性にかける値を持たせておいて、値が入力された際に、data属性のあるtdタグのテキストを書き換えていく感じです。
Reactの場合
こちらがReactでの実装です。説明しやすさも考えてTypeScriptで書いてます。
See the Pen kuku_React by nabekou (@nabekou29) on CodePen.
解説
コンポーネントの定義.tsxconst App: React.FunctionComponent<{defaultValue: number}> = ({defaultValue}) => {...}この関数がコンポーネントです。(Classで定義する方法もあります。)
属性を引数に受け取ることができます。状態を扱う.tsxconst [num1, setNum1] = React.useState(defaultValue);
React.useStateを使うことでこのコンポーネントで状態を持つことができます。
num1が状態、setNum1がnum1を更新するための関数です。setNum1を通して値を更新することでReactに値が変わったことを知らせています。JSX.tsxreturn ( <div> <input type="number" defaultValue={num1} onChange={handleNum1Change} />の段 <table> <tbody> <tr><th>1</th><td>{1 * num1}</td></tr> {/* 省略... */} </tbody> </table> </div> );関数のコンポーネントでは、React要素を返します。React要素はJSXにより作成可能です。コンパイルすることでReact要素を作成する関数に変換されます。変換後は「View Compiled」から確認できます。
このHTMLっぽい書き方がJSXです。あくまでそれっぽいだけなので、本来存在しない属性や一部名前が変わっている属性が存在します。JSXのなかで{}で囲まれた中にJSを書くことが可能です。当然そのJS内でもJSXをかけます。データバインディング.tsxconst handleNum1Change = (event: React.ChangeEvent<HTMLInputElement>) => { setNum1(Number(event.target.value)); }値を入力した時に
num1を更新します。setNum1を使って更新することで表の値が更新されます。ReactDOM.render( <App defaultValue={1}/>, document.getElementById('app') );React要素を
#appに描画しています。ちなみに
before.tsx<table> <tbody> <tr><th>1</th><td>{1 * num1}</td></tr> <tr><th>2</th><td>{2 * num1}</td></tr> <tr><th>3</th><td>{3 * num1}</td></tr> ... <tr><th>9</th><td>{9 * num1}</td></tr> </tbody> </table>jQueryでの実装に合わせて書きましたが、JSのArrayを用いて以下のように書くことが出来ます。
after.tsx<table> <tbody> { [1, 2, 3, 4, 5, 6, 7, 8, 9].map(num2 => ( <tr key={num2}><th>{num2}</th><td>{num1 * num2}</td></tr> )) } </tbody> </table>比べてみる
- Declarative (宣言的なView)
- Component-Based (コンポーネントベース)
上の2つを意識して比べてみようと思います
宣言的なView 的には?
特に
<td>{num1 * num2}</td>のところがとてもわかりやすくなっていると思います。表に何が表示されるかがより明らかになりました。
また、更新がとても楽になっています。jQueryでは各td要素にアクセスして計算・更新をしていましたが、Reactではnum1を更新するだけで表の値が更新されています。コンポーネントベース 的には?
見た目と動きが一体となったことで、状態の変化がどのように行われて、どういった作用をもたらすのかがぱっと見で分かるようになりました。JSとHTMLを行ったりきたりする必要が無くなりました。
さらに、変にセレクタを書いたりする必要性が無くなりました。どうせ密な結合になるならこっちの方がわかりやすいと思います。おわりに
Reactは学習コストが重いと良く言われている気がしますが、覚えることは圧倒的に少ないです。ただJSに対する知識がそれなりに求められるので、そこが難しいところなのかなと思います。Hooksの登場で書きやすさもかなり向上したので、これからも周りに布教していきたいと思います。
次はもう少し実装よりの話をする予定です。参考
React公式
Reactを使うとなぜjQueryが要らなくなるのか
ReactとVueのどちらを選ぶか
フロントエンドのコンポーネント設計に立ち向かう
- 投稿日:2019-10-28T05:16:19+09:00
対象要素までヌルッと自動スクロールするメソッドの書き方!
まえがき
無限番煎じですが、調べるのに地味に時間がかかったので、自分用を兼ねて残しておきます。
コード
scrollToTarget() { const element = document.getElementById("target-id") const rect = element.getBoundingClientRect() const currentScrolledHeight = window.pageYOffset || document.documentElement.scrollTop const position = window.innerHeight * 0.9 window.scrollTo({top: rect.bottom + currentScrolledHeight - position, behavior: "smooth"}) }このメソッドが実行された際の動きとしては
idがtarget-idである要素の下部が、ウインドウの上から90%のところに来るようにヌルッとスクロールします。理屈
- idが
target-idである要素の相対座標とサイズを取得する(rect)- すでにスクロールされている高さを取得(
currentScrolledHeight)- ウインドウサイズの上から90%の位置取得(
position)target-idである要素の下部の相対座標 + すでにスクロールされている高さ =target-idである要素の下部の絶対座標window.scrollToは、指定座標がウインドウの一番上にくるようにスクロールするため、positionを引いて位置調整する- デフォルトだと、一瞬でスクロールするせいで画面がどの位置なのか混乱を招くので、
behavior: "smooth"を指定することでスクロールアニメーションでヌルッとさせるあとがき
scrollToの引数のtopの部分をleftにしたら横スクロールになったり、
rect.bottomをrect.topにすることでターゲットの上部座標を基準にできたりしますので、都度改造して使ってください!
- 投稿日:2019-10-28T01:11:59+09:00
potision:fixed したheaderがページ全体のmin-widthを下回った時にスクロールできなくなってしまう
「position: fixed」でスクロール固定したヘッダーを横にスクロールさせる
この記事が参考になる。
.header { position: fixed; top: 0; min-width: 1000px; }みたいに固定されてて
min-width指定してあると、min-width下回ると普通のコンテンツは横スクロール出来るが、fixedのコンテンツは出来ない。
なので、fixedしてあるheaderだけが横スクロールできなくなってしまう。本来は中身のコンテンツを横スクロールしたらheaderも横スクロールしたい。
なので、その時は$(window).on("scroll", function(){ $(".header").css("left", -$(window).scrollLeft()); });のようにJSで動かしてあげる。
windowをscrollしたら、.headerのcssにleftを入れてあげる。
その値はwindowの横スクロールした値の逆。scrollLeftは右に画面がいった値(左にスクロールした値)なので、その逆をleftに入れてあげる。
100px左にスクロールしたらleftに-100px。これで実質同じ動きが実現できます。




























