20191028のJavaScriptに関する記事は29件です。

WebComponents だけでアプリの骨格を作ってみる

はじめに

WebComponents、何がいいかというとブラウザでネイティブに対応してるとこですね。

そろそろ本家、3rdパーティーともライブラリが充実してきて、Vue とか React とかのフレームワークを使わずに WebComponents だけでいけそうな感じになってきたので、とりあえず下のようなアプリの骨格を作って検証してみます。

画面収録-2019-10-28-13.05.57.gif

オフィシャルの material.ioGitHub にはメジャーな

  • 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 init

es-dev-server をインストールして動かしておきます。

$ npm install -g es-dev-server
$ es-dev-server --node-resolve --watch

8000 番のポートが使われてなければ
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.js
class
Main extends HTMLElement {
    constructor() {
        super();
        this.attachShadow( { mode: 'open' } ).innerHTML = '<div>MAIN</div>'
    }
}
customElements.define( 'jp-main', Main )
About.js
class
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 を更新する際に既存のエレメントを再利用して描画の高速化を図るようなベースクラスをたぶんそのうち誰かが作ってくれる気がします。もし知っていたら教えてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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つのファイルを作成する。

Dockerfile
FROM 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 . /app

docker-compose.yml
version: '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:
Gemfile
source '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.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: db

passwordとhostの部分をdocker-compose.ymlの記述と合わせます。

$ docker-compose upコマンドでコンテナを立ち上げ、localhost:3000に接続し、RailsのHello World画面が表示されれば無事成功です!

Vue.jsの導入

vue.jsをRailsで使用できるようにするためにまずはWebpackerの導入が必要。Gemfileに以下を追加します。

Gemfile
gem '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) を導入する

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【jQuery】いい感じのアコーディオンを自作した

はじめに

アコーディオン(開閉メニュー)を実装するとき、jQuery標準APIの slideUp() / slideDown() だと融通がきかなかったりするので簡単なスニペットを作りました。残念ながらjQuery依存です。そしていい感じかはわかりません。

使い方

  1. jQueryとpublic/assets/js/app.jsを読み込む
  2. 対象要素に下記を指定する
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クラスのコードです。

js
class 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;

まとめ

大したものでは無いのですが、勉強がてらアコーディオン機能を作ってみました。
小さな機能に関しては自作してしまったほうが早かったり融通がきいたりするので、どんどん自作コードで身を固めていきたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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.ts
export 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);
    }
  } 
}

完成品

こちらが完成品です。
いかがでしょうか?

bingo_sample.gif

おわりに

ionicどころかフロントサイドを触り始めたばかりなので、
もっとこうしたほうがいいなどあればぜひ教えてくださると嬉しいです。
読んでいただきありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

発火とは

はじめに

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を訳したものだそうです。
これでだと訳した方も、原文の方も「もっと正確な言葉があったのでは?」と思ってしまうので、
もしかしたら違う理由があるのかもしれません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsのDynamic Routing + Static HTML exportを組み合わせて使う

Next.js 9ではDynamic Routingが標準でサポートされ使用感が大きく改善されました。
このDynamic Routingで構築したアプリをexportした場合に一手間必要だったので考慮したことをまとめておきます。

この構成でデプロイを行う際には、ビルド時にデータを全部仕込むか、クライアント側で(APIを叩くなどして)データを仕込むかで大きく対処方法が変わります。

今回の想定アプリ

次のようなページを持つアプリケーションを想定します。

/
/about
/show/[id]

1.ビルド時にデータを仕込む方法

exportする際には、next.config.jsexportPathMapを利用し、出力するファイルの調整を行っていきます。
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をする際に移行コストが小さくなるのでおすすめです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者によるプログラミング学習ログ 139日目

100日チャレンジの139日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。

100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。

139日目は、

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ink Recognizer REST API を使用してデジタル インクの認識アプリを作成する

今回は比較的新しい Ink Recognizer を使用して、手書きで書いた文字を認識するアプリを作成してみようと思います。

Ink Recognizer をデプロイする

Azure Portal から Ink Recognizer をデプロイします。新しいリソースの作成で、「Ink」と入力すると出てくるので、任意の名前を付けてデプロイします。この記事を執筆時点ではまだプレビュー版でした。(2019/10/26時点)
image.png
デプロイが終わり構築されたリソースのページ移動すると、API に接続するための Key が表示されています。後程使用するのでこれをメモしておきます。
image.png

サンプルコードを 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.js
SERVER_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 を返すところです。
image.png

実際に「Hello」と手書きで書いてみました。右側のボックスには認識された JSON コードが返ってきていました。中身を見るとちゃんと「hello」と認識されていることが分かります。
image.png

さいごに

今回は初めて InkRecognizer を使用してみましたが、API のように非常に簡単に扱うことができるので、色々使い道はあるかなと思いました。次回の記事では日本語をどれくらい認知できるのかどうかについて探求しようと思います。

参考 URL

InkRecognizer Cognitive Service JavaScript Sample
https://github.com/Azure-Samples/cognitive-services-REST-api-samples/tree/master/javascript/InkRecognition/javascript-app

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebGL(GLSL)でカラースケール関数を実装する

カラースケール概要

データの値に応じて色を変えたい場合、下図のようなカラースケールを用いることがある。温度によって色分けされたサーモグラフィーの画面や、気温に応じて色分けされた地図などを思い浮かべれば良い。
ダウンロード.png

カラースケールは大きく分けて以下の3種類に区分することができる。

  • 1. Categorical
    • 質的・離散的な色分け。例えば折れ線グラフの1本目、2本目、...を色分けする場合などに用いる。 category10.png
  • 2. Diverging
    • ある値を境として2色に分岐する。例えば赤字/黒字の程度などを示す場合に用いる。
  • 3. Sequential
    • 連続的な色分け。例えば温度の高さを青〜黄〜赤で表現する場合など。単色のグラデーションも含む。
    • スクリーンショット 2019-10-28 20.54.46.png

このうち、Categoricalなカラースケールは単にRGBの配列を用意することで容易に実装できるが、残りのDiverging, SequentialRGBの連続的な補間が必要になり、言語ごとに実装が必要になる。このような連続的カラースケールを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);
}

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LIFF v2 で追加されたQRコードリーダーを使ってみる

はじめに

先日、LIFF v2がリリースされ、外部ブラウザ対応やQRコードリーダー等便利な機能が追加されました。

スクリーンショット 2019-10-28 12.01.01.png (333.7 kB)

ということなので、QRコードリーダーを使ってみます。

できたもの

以下の友だち追加ボタンから、実際に試していただけます。

友だち追加

手順

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.js
window.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権限を付与する必要があることと、

image.png (289.7 kB)

LIFFのサイズやエンドポイントURLを編集した際に、なぜかScanQR権限が勝手にOFFに戻ってしまうので、ScanQR権限の状態は要注意ですw
(そのうち改善されるはず!!!)

ezgif-6-404dd4c4468c.gif

わかったこと

  • QRの読み取りめっちゃはやい
  • こちらのサイトで作成した、テキストを埋め込んだQRコードは読み取りできなかった。。

Screenshot_20191028-182332.png (1.9 MB)

  • ありきたりだけどQRコードスタンプラリーとか作れそう「LINEでQRスタンプラリー!!」

手軽にQRコードリーダーが使えるのはいいですね!

LIFF v2 ではQRコードリーダー以外の機能も追加されています。
@sumihiro3 さんの記事(LIFF v2 で追加されたAPIを試してみる)がわかりやすいので、参考にしてみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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側の処理の流れを把握する。

sample_01.jpg

WebGLの初期化の流れ

  • とにかく工程が多い!(それはどの3D APIでも同じ)
  • GPUで動作するGLSLCPU側のロジックが連携するために必要な手順を確認していく。
  • 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.glsl
precision 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.glsl
attribute 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に送られている情報を使ってレンダリングされる。
  • VBOattributeロケーションを使って頂点を有効にする => 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.glsl
attribute 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.glsl
precision mediump float;

uniform vec4 globalColor;
varying vec4 vColor; // varying変数間で変数名は同一でなければならない

void main(){
  gl_FragColor = globalColor * vColor;
}

sample_02.jpg

頂点にポイントサイズの頂点属性を追加する(サンプル003)

  • floatの頂点属性なので、ストライドが1になる。
  • 頂点を描画する形式をプリミティブと呼ぶ。
    • サンプルで使われているgl.POINTSのほか、gl.LINESgl.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.glsl
attribute 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.glsl
precision mediump float;

uniform vec4 globalColor;
varying vec4 vColor;

void main(){
  gl_FragColor = globalColor * vColor;
}

sample_03.jpg

カーソルに連動して動くサンプルを作ってみる(サンプル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.glsl
attribute 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.glsl
precision 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.glsl
attribute 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.glsl
precision mediump float;

uniform vec4 globalColor;
varying vec4 vColor;

void main(){
  gl_FragColor = globalColor * vColor;
}

やってみよう 水平に並べる(サンプル006)

  • 水平に左から右まで等間隔に頂点を並べる。
vert.glsl
attribute 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;
}

sample_06.jpg

やってみよう サイン波で揺らす(サンプル007)

  • 水平に並べた後、サイン波で揺らしてみる。
  • uniform変数を使って時間の経過(秒単位)が送られてくる。
vert.glsl
attribute 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;
}

sample_07.jpg

やってみよう 円形に配置する(サンプル008)

  • サインとコサインを組み合わせて円を作る。
vert.glsl
attribute 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;
}

sample_08.jpg

最後に

誤字・脱字などありましたら、すみません…

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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を返してくれます。
処理が長かったり、他でも使う場合は関数として切り出した方が良いと思いますが、
他で使う予定がない処理の場合ささっと無名関数で書いてしまうのもアリだと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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); // 9

13. 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); // 14

Conclusion

この記事では、ちょっとしたコーディングに役立ち、コードを短く簡潔に保つための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); // []

false0''nullNaNundefinedのうち、NaNだけ==falseがfalseになる。
NaNは自分自身も含めたあらゆる値との比較がfalseになるので仕様なのだがちょっとわかりにくい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ESLint v6.6.0

v6.5.0 | 次 (2019/11/23 JST)

ESLint 6.6.0 がリリースされました。
小さな機能追加とバグ修正が行われています。

これまで ESLint は隔週リリースを行ってきましたが、アクティブなメンテナが減少していることから、今後は月間リリースとなります。

質問やバグ報告等ありましたら、お気軽にこちらまでお寄せください。

? 日本語 Issue 管理リポジトリ
? 日本語サポート チャット
? 本家リポジトリ
? 本家サポート チャット

? 本体への機能追加

context.getCwd()

? RFC035, #12389

各ルールから現在のディレクトリを取得できるようになりました。

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
}

Online demo

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)

Online demo

no-unsafe-negation enforceForOrderingRelations

? #12414

比較演算子の左辺をオペランドとする否定演算子を警告するオプションが追加されました。

/*eslint no-unsafe-negation: [error, { enforceForOrderingRelations: true }] */

//✘BAD
if (! a < b) {}

//✔GOOD
if (!(a < b)) {}

Online demo

no-extra-parens enforceForNewInMemberExpressions

? #12436

new式に直接プロパティアクセスを繋げているときに、そのnew式を囲む括弧を無視するためのオプションが追加されました。

/*eslint no-extra-parens: [error, all, { enforceForNewInMemberExpressions: false }] */

//✔GOOD
var obj = (new Foo()).prop;

Online demo

✒️ eslint --fix をサポートしたルール

特になし。

⚠️ 非推奨になったルール

特になし。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

export default しているものをasを使ってimportしたい

都合でdefaultでexportしている名称と同じ名称の変数などを定義したい場合、
適当な名前で適当に回避していましたがちゃんと対応できました(勉強不足がすぎる)。


下記のようなmoduleをimportしたい場合、
import先で同じ名前の変数があったりすると重複してしまい困ったことになりますが

importしたいmodule
export 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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

npm
npm i flatpickr --save
resources/js/app.js
import 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.js
flatpickr(".js-calendar", {
        plugins: [
            new monthSelectPlugin({})
        ],
      })

コンパイルして表示を確認します

npm run dev

日本語化しているとこのような表示になります。
SC000006.JPG

参考にしたサイト

https://github.com/flatpickr/flatpickr/issues/269

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】Composition APIを利用したコンポーネントの作成方法について

Composition APIとは

従来のdata, computedを用いたAPIによる問題を改善するための、Vue.jsの新たなコンポーネント作成方法です。
https://vue-composition-api-rfc.netlify.com/#summary

Vue3.xで使用可能になるとのことですが、以下のプラグインを使用することで、Vue2.xでも利用可能です。
https://github.com/vuejs/composition-api

Composition 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機能の修正のためにdatamethodsをスクロールで往復する状況が改善されるかもしれません。
(そもそも、そのようなコンポーネントは再設計した方がいいかもしれませんが...)

以上です。ありがとうございました。

参考記事

https://qiita.com/ryo2132/items/f055679e9974dbc3f977
https://vue-composition-api-rfc.netlify.com/#api-introduction

参考にさせていただきました。ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HTMLレンダリングエンジン

目次

  • ブラウザとは(ブラウザコンポーネント)
  • ブラウザとサーバーの通信
  • HTMLレンダリングエンジン
  • JavaScriptエンジン
  • 日本ブラウザシェア率(2016~)

ブラウザとは

ウェブブラウザ(インターネットブラウザ、web browser)とは、 World Wide Web (WWW) の利用に供するブラウザであり、ユーザエージェント (UA) である。具体的には、ウェブページを画面や印刷機に出力したり、ハイパーリンクをたどったりするなどの機能がある。単にブラウザ(ブラウザー)と呼んだ場合、多くはウェブブラウザのことを指す。

出典: フリー百科事典『ウィキペディア(Wikipedia)』


ブラウザコンポーネント

1_lmbu87mtesvfqqbfmum-ka_m.png

出典: ブラウザはどのようにコンテンツをレンダリングしているのか?


各コンポーネント

  • ユーザインターフェース
    • ユーザは操作できる部分:アドレスバー、戻る/進むボタン、メニューなど
  • ブラウザ エンジン
    • ユーザインターフェースとレンダリングエンジンの間の情報の受け渡しを担当
  • ⭐️HTMLレンダリングエンジン
    • HTMLとCSSを解析し、画面に表示
  • ⭐️JavaScriptエンジン
    • JavaScriptを動かしを担当
  • ネットワーキング
    • HTTPリクエストなどのネットワーク呼び出しを担当
  • UIバックエンド
    • チェックボックスやウィンドウなどのコアウィジェットの描画を担当
  • データストレージ
    • Cookieなどのデータの保存を担当

ブラウザとサーバーの通信

20190413002537.jpg

  1. アドレスバーにアクセス先を入力
  2. ネットワーク経由でサーバーへリクエスト
  3. リクエスト内容より処理してHTMLを生成
  4. CSSとJSファイルなどもクライアントへ返信
  5. レンダリングエンジンとJavaScriptエンジンより画面を表示

出典: Webフロントエンドとサーバーサイドの技術動向をざっくり整理する


HTMLレンダリングエンジン

HTMLレンダリングエンジンとは、ウェブページ記述用言語で書かれたデータを解釈し、実際に画面に表示する文字や画像などの配置を計算するプログラムである。HTMLに加え、CSS、XMLなどを解釈できるものが多い。

出典: フリー百科事典『ウィキペディア(Wikipedia)』


主なHTMLレンダリングエンジン

Trident (Internet Explorer、QQ浏览器) - トライデント
    EdgeHTML (Microsoft Edge) - エッジ
Gecko (Firefox) - ゲッコー
    Servo - サーボ
KHTML (Konqueror)
    WebKit (Safari) - ウェブキット
        Blink (Chromium, Chrome, Opera, Microsoft Edge) - ブリンク

HTMLレンダリングエンジンの歴史

スクリーンショット 2019-10-27 17.12.18.png

参照:Browser engine From Wikipedia, the free encyclopedia
補足: EdgeブラウザのChromiumベース移行、マイクロソフトが正式発表


JavaScriptエンジン

JavaScriptエンジンとはJavaScriptコードを実行するプログラムまたはインタプリタのことです。

出典:V8エンジンでのJavaScriptの機能と最適化コードの書き方に関する5つのベストプラクティス


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~)

スクリーンショット 2019-10-27 17.48.36.png

補足:Desktop、Mobile、Tabletを含む
[出典:https://gs.statcounter.com]


ご静聴ありがとうございます

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者】営業から転職のために【備忘録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; の+と=の間にスペースを入れるとエラーが出たので気を付けること!
  • 定数での${}で連結する際は `` を文頭文末に入れ忘れないように気を付ける。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

awaitできるsetTimeoutを1行で書く方法

awaitすることができるsetTimeoutを1行で書くイディオムです。

await new Promise(resolve => setTimeout(resolve, 3000)) // 3秒待つ

// ... 3秒後の処理 ...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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)とは簡単に言うと負荷を肩代わりしてくれるレンタルサーバのようなものです。下図のように一部の負荷を肩代わりしてもらうおかげで自分のサーバの負荷が下がり、安定してサービスを提供できるようになります。
スクリーンショット 2019-10-27 14.30.31.png

では、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);
    }
  });
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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
# 以下略

デベロッパーコンソールとかで適当に試してみてください

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Array.prototype.fill()にオブジェクトをそのまま渡してハマった

知らなくてハマったのでこれから知らなくてハマる人のために書きます。

顛末

最初は単純な二次元配列でやっていたところを状態持たせようとオブジェクトに変更してハマりました。お馬鹿さんですね。

maze-text-generator | m19e

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
エンジニアのハマり時間とその技術的難易度の相関関係

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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を使うと何がいいのか?

こちらも公式ドキュメントのトップから抜粋

  1. Declarative (宣言的なView)
  2. Component-Based (コンポーネントベース)

もう1つ「Learn Once, Write Anywhere (一度学習すれば、どこでも使える)」ということが挙げられていますが、これは一旦スルーで。

Declarative (宣言的なView)

まず「宣言的」って何?というとこですね。
Reactで実装する際に私たちが実装するのは「ある状態において何が表示されるべきか」です。
「この値をここに表示して」「この場合はこっちを表示して」というのをただただ宣言していけば良いわけです。

そしてその宣言通りに効率良くUIの更新(DOMの操作)をする必要があります。
そこで登場するのが仮想DOMです。仮想DOMは通常のDOMと同じツリー構造をとります。
仮想DOMが構築し直されると、差分のみが実際のDOMに反映される、といった具合です。

v-dom.png

仮想DOMの構築はサーバーサイドレンダリング(SSR)でテンプレートエンジンを使ってHTMLを生成するのに良く似ています。SSRでの描画で画面の値が不整合になったなんていう記憶はないので、それに近い方法を取れる仮想DOMを使わない手はありません。
加えて、必要な箇所のDOMの更新しか行われないため、下手なピュアのJSよりもパフォーマンスが向上します。

Component-Based (コンポーネントベース)

ReactではUIの状態、見た目、動きをコンポーネントという単位で分割します。単純な入力欄からページまで全てをコンポーネントとみなします。コンポーネントを組みわせていくことでUIを構築していきます。
分割することで再利用が可能になり、全体的な見た目の統一感を生みます。

コンポーネントは自身の状態を管理し、見た目と振る舞いを定義します。そのため状態をDOMに置くことがなくなります。DOMにある値はどこからでも変更可能ですが、コンポーネントの状態はアクセスできるスコープが限られるので状態の遷移が追いやすくなります

Reactを書いてみる

九九を表示するアプリを考える

  1. 何の段を表示させるかを入力する
  2. 入力された段の九九の表を表示する

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.

解説

コンポーネントの定義.tsx
const App: React.FunctionComponent<{defaultValue: number}> = ({defaultValue}) => {...}

この関数がコンポーネントです。(Classで定義する方法もあります。)
属性を引数に受け取ることができます。

状態を扱う.tsx
const [num1, setNum1] = React.useState(defaultValue);

React.useStateを使うことでこのコンポーネントで状態を持つことができます。
num1が状態、setNum1num1を更新するための関数です。setNum1を通して値を更新することでReactに値が変わったことを知らせています。

JSX.tsx
return (
  <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をかけます。

データバインディング.tsx
const 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>

比べてみる

  1. Declarative (宣言的なView)
  2. Component-Based (コンポーネントベース)

上の2つを意識して比べてみようと思います

宣言的なView 的には?

特に<td>{num1 * num2}</td>のところがとてもわかりやすくなっていると思います。表に何が表示されるかがより明らかになりました。
また、更新がとても楽になっています。jQueryでは各td要素にアクセスして計算・更新をしていましたが、Reactではnum1を更新するだけで表の値が更新されています。

コンポーネントベース 的には?

見た目と動きが一体となったことで、状態の変化がどのように行われて、どういった作用をもたらすのかがぱっと見で分かるようになりました。JSとHTMLを行ったりきたりする必要が無くなりました。
さらに、変にセレクタを書いたりする必要性が無くなりました。どうせ密な結合になるならこっちの方がわかりやすいと思います。

おわりに

Reactは学習コストが重いと良く言われている気がしますが、覚えることは圧倒的に少ないです。ただJSに対する知識がそれなりに求められるので、そこが難しいところなのかなと思います。Hooksの登場で書きやすさもかなり向上したので、これからも周りに布教していきたいと思います。
次はもう少し実装よりの話をする予定です。

参考

React公式
Reactを使うとなぜjQueryが要らなくなるのか
ReactとVueのどちらを選ぶか
フロントエンドのコンポーネント設計に立ち向かう

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

対象要素までヌルッと自動スクロールするメソッドの書き方!

まえがき

無限番煎じですが、調べるのに地味に時間がかかったので、自分用を兼ねて残しておきます。

コード

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.bottomrect.topにすることでターゲットの上部座標を基準にできたりしますので、都度改造して使ってください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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で動かしてあげる。
windowscrollしたら、.headercssleftを入れてあげる。
その値はwindowの横スクロールした値の逆。

scrollLeftは右に画面がいった値(左にスクロールした値)なので、その逆をleftに入れてあげる。
100px左にスクロールしたらleftに-100px。

これで実質同じ動きが実現できます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む