20191223のJavaScriptに関する記事は30件です。

10. 行数のカウント

10. 行数のカウント

行数をカウントせよ.確認にはwcコマンドを用いよ.

Go

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    //  読み込みファイルを指定
    name := "../hightemp.txt"
    line := 0

    //  読み込むファイルを開く
    f, err := os.Open(name)
    if err != nil {
        fmt.Printf("os.Open: %#v\n",err)
        return
    }
    defer f.Close() //  終了時にクリーズ

    //  スキャナライブラリを作成
    scanner := bufio.NewScanner(f)

    //  データを1行読み込み
    for scanner.Scan() {
        line++;
    }

    //  エラーが有ったかチェック
    if err = scanner.Err(); err != nil {
        fmt.Printf("scanner.Err: %#v\n",err)
        return
    }

    //  行数を表示
    fmt.Println("Line",line)
}

python

import sys

line = 0

# ファイルを開く
with open("../hightemp.txt", "r") as f:
    # 一行ずつ読み込む
    for data in f:
        # 行数を加算
        line += 1

# 行数を表示
print("Line",line)

Javascript

// モジュールの読み込み
var fs = require("fs");
var readline = require("readline");

var line = 0;

//  ストリームを作成
var stream = fs.createReadStream("../hightemp.txt", "utf8");

//  readlineにStreamを渡す
var reader = readline.createInterface({ input: stream });

//  行読み込みコールバック
reader.on("line", (data) => {
    line = line + 1
});

//  クローズコールバック
reader.on("close", function () {
    console.log("Line",line);
});

まとめ

やっと 「第2章: UNIXコマンドの基礎」 へ突入!!。

2章に入ったとのことで、Pythonのバージョン設定をやっと 3.7 へ変更しました。
IDEの設定だけですけど・・・。設定がどこにあるか探すのが・・・。と言い訳。

ファイルの読み込みをそれぞれの言語調べながら。
Go,Python はそれほど困らなかったが、
Javascirpt は。おぉおぉ。なんか面白い。非同期との事もあり考え方は注意が必要か。

補足

Go 言語で変数名を fname としていたが、IDE(Golang) が typo? と言ってくる。
有り難いのかなぁ。とりあえず name へ変更し回避。

トップ

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

年末まで毎日webサイトを作り続ける大学生 〜66日目 オブジェクト指向で一桁の足し算ゲームを作る〜

はじめに

こんにちは!@70days_jsです。
今日は1桁の足し算ゲームを作りました。(gif)↓
test3.gif

正解が1桁だと数字が表示される前に消えちゃってますね・・・。

66日目。(2019/12/23)
よろしくお願いします。

サイトURL

https://sin2cos21.github.io/day66.html

やったこと

足し算ゲームを作りました。

html↓

  <body>
    <h2>Addition Game</h2>
    <div>count: <span id="countDiv">0</span></div>
    <div class="wrapper">
      <div id="one" class="question"></div>
      <div id="two" class="question"></div>
      <div id="three" class="question"></div>
    </div>
  </body>

css↓

body {
  font-family: "ヒラギノ明朝 ProN", "Hiragino Mincho ProN", sans-serif;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}
.wrapper {
  margin-top: 30px;
  display: flex;
  justify-content: center;
}

.question {
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 30px;
  border: solid 1px black;
  width: 200px;
  height: 100px;
}

JavaScript↓

let div1 = document.getElementById("one"),
  div2 = document.getElementById("two"),
  displayDiv = [div1, div2],
  div3 = document.getElementById("three"),
  countDiv = document.getElementById("countDiv"),
  mathRange = 9, //問題文の項にはこの数値以下を使う
  mathObject = 2, //項の数
  koArray = [], //項の値を持つオブジェクトの配列
  koSumNumber, //項の合計値===答え
  inputMathArray = [], //入力された数字の値を持つオブジェクトの配列
  inputSumNumber, //入力された数字の合計値
  keyCodeMath = {
    48: 0,
    49: 1,
    50: 2,
    51: 3,
    52: 4,
    53: 5,
    54: 6,
    55: 7,
    56: 8,
    57: 9
  },
  count = 0;

//_________________________________________________________
//数値を持つオブジェクト
function Question(number) {
  this.math = number;
}

Question.prototype.randomSet = function() {
  this.math = Math.ceil(Math.random() * mathRange); //ceil →切り上げ, randam()→ 0~1までの数字をランダムにreturnする/ 1~10が表示される
};

for (var i = 0; i < mathObject; i++) {
  let math = new Question(Math.ceil(Math.random() * mathRange));
  koArray.push(math);
  displayDiv[i].innerHTML = math.math;
}

//_________________________________________________________
document.addEventListener("keydown", logKey);

//入力文字を表示するための関数
function displayThree() {
  div3.innerHTML = "";
  for (var i = 0; i < inputMathArray.length; i++) {
    div3.innerHTML += inputMathArray[i].math;
  }
}

//入力値が正解か判定する関数
function checkCorrectAnswer() {
  koSumNumber = 0;
  inputSumNumber = "";
  for (var i = 0; i < koArray.length; i++) {
    koSumNumber += koArray[i].math;
  }
  for (var i = 0; i < inputMathArray.length; i++) {
    inputSumNumber += String(inputMathArray[i].math);
  }
  if (Number(inputSumNumber) === koSumNumber) {
    for (var i = 0; i < mathObject; i++) {
      koArray[i].randomSet();
      displayDiv[i].innerHTML = koArray[i].math;
      document.body.style.backgroundColor = "rgba(250,0,0,.3)";
    }
    div3.innerHTML = "";
    inputMathArray.length = 0;
    count++;
    countDiv.innerHTML = count;
  } else {
    document.body.style.backgroundColor = "rgba(0,0,250,.3)";
  }
}
//入力時の動作
function logKey(e) {
  //消す場合
  if (e.keyCode === 8) {
    if (inputMathArray) {
      inputMathArray.pop(); //入力オブジェクトを一つ消す
      displayThree();
    }
  } else if (e.keyCode >= 48 && e.keyCode <= 57) {
    let answer = new Question(keyCodeMath[e.keyCode]);
    inputMathArray.push(answer); //入力オブジェクトを配列に入れる
    displayThree();
    checkCorrectAnswer();
  }
}

htmlとcssは特に変わったところはありません。

JavaScriptの方は、今回数字を入力するのでkeyCodeMath変数にkeyCodeの値と、それが示す数字をhashで入れています。
checkCorrectAnswer()関数で正解かどうかを判定しています。

入力文字と正解の数字を合致させるために、一度入力文字をstringにして、そのあと結合済みの入力値をNumberに直しています。

Stringにする↓

inputSumNumber += String(inputMathArray[i].math);

Numberに直す↓

if (Number(inputSumNumber) === koSumNumber) {

もし不正解だった場合は背景を青色に変えています。↓

else {
document.body.style.backgroundColor = "rgba(0,0,250,.3)";
}

ちなみに、正解すると赤にしています。

感想

すぐできるだろうと思ったら予想外に時間がかかってしまいました。
効率的に作れるように頑張ろう。

最後まで読んでいただきありがとうございます。明日も投稿しますのでよろしくお願いします。

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

JavaScript の日付処理は luxon が便利

はじめに

JavaScript で日付処理といえば moment.js が有名かと思います。
そんな中、私の関わるプロジェクトでは使用するライブラリを、 moment.js -> date-fns -> luxon と変更してきました。
そして今現在は luxon を使っています。

luxon は使っていて便利だな、と感じるライブラリです。
ここでは、これまで使用した処理の中で、私が便利だと思った機能をいくつか紹介します。
(基本的なものは割愛します。下記の参考リンクにもまとめられています。)

実際に使っている処理

DateTime 型を作成

これは基本的なものですが、これがないと始まらないので記載しておきました。

import { DateTime } from 'luxon';

// ISO format
DateTime.fromISO('2019-12-23T12:00:00.000000');
// JavaScript Date format
DateTime.fromJSDate(new Date());
// SQL format
DateTime.fromSQL('2017-05-15 09:12:34');

表示用に出力

toLocalString が個人的に好きです。
ローカライズにも対応しているのがありがたい。

const dateTime = DateTime.fromISO( ... );

// ISO 形式で出力
dateTime.toISO();
// 指定したフォーマット形式で出力
dateTime.toFormat('h:mma, dd-MM-yyyy');
// 定められたフォーマットで出力(ローカライズ対応)
dateTime.toLocaleString({ ...DateTime.DATE_MED });
/*
 * DATETIME_FULL
 * DATETIME_FULL_WITH_SECONDS
 * DATETIME_HUGE
 * DATETIME_HUGE_WITH_SECONDS
 * DATETIME_MED
 * DATETIME_MED_WITH_SECONDS
 * DATETIME_SHORT
 * DATETIME_SHORT_WITH_SECONDS
 * DATE_FULL
 * DATE_HUGE
 * DATE_MED
 * DATE_SHORT
 * TIME_24_SIMPLE
 * TIME_24_WITH_LONG_OFFSET
 * TIME_24_WITH_SECONDS
 * TIME_24_WITH_SHORT_OFFSET
 * TIME_SIMPLE
 * TIME_WITH_LONG_OFFSET
 * TIME_WITH_SECONDS
 * TIME_WITH_SHORT_OFFSET
 */

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
dateTime.toLocaleString({ year: 'numeric', month: 'short', day: 'numeric' }),

○○分以内に発生したイベントの判断

現在時刻との比較はよく使うので diffNow は便利ですね。

if (DateTime.fromISO(...).diffNow('minutes').minutes >= -3) { ... }
if (DateTime.fromISO(...).diff(..., 'minutes').minutes >= 60) { ... }

// DateTime.fromISO(...).diffNow().minutes とした場合、返り値が 0 になりました。
// これは DateTime オブジェクトは millisecond がデフォルトのためです。
// 単位を分にする場合は diffNow('minutes') と記載する必要があります。

祝日の判断

営業日判断などで利用します。

holidays.find((holiday) => {
  return holiday.hasSame(DateTime.local(), 'day');
} !== undefined

○○年後の開始月/日を取得

startOfendOf は非常に便利だと思います。
応用すれば、今月の初日や、現在時刻から00分や00秒の取得、といったこともできます。

// 15年後の同月一日を取得(現在が2019年12月なら2034/12/1)
DateTime.utc().plus({ years: 15 }).startOf('month');
// 15年後の開始日を取得(現在が2019年12月なら2034/1/1)
DateTime.utc().plus({ years: 15 }).startOf('year');

ハマったところ

diff/diffNow

フォーマットを指定しないと、milliseconds 以外の値が返ってこない、という状況で最初ハマりました。
差分を取得しているつもりなのに、if文が正しく動いてくれません。
分で評価したい場合は、diffNow('minutes').minutes とする必要があります。

ユニットテスト

我々のプロジェクトでは、サーバサイドでも日付時刻計算に luxon を使っています。
ローカルでテストコードを実行してユニットテスト成功していたものが、CI/CD環境ではエラーとなるケースに直面しました。
どの環境でも同じように動くように実装する必要性を強く感じました。

参考

逆引きLuxon集 〜moment.jsから乗り換えるために〜

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

【Vue.js】エラーの表示を簡単に実装してみる ~ vue-toasted ~

概要

本記事では、Vue.jsで使える便利なNotificationライブラリである vue-toastedについてまとめていきます。

vue-toasted って?

vue-toasted とは、概要にも記述した通り、Vue.jsで使えるNotificationライブラリです。
実装自体も簡単で、3分あれば実装できてしまうとても便利なものです。
トーストでお知らせを表示させたい時にぜひ使ってみてください!

公式ページ : https://www.npmjs.com/package/vue-toasted

環境

Vue.js 2.9.6

インストール

yarn、npm、CDNののいずれかでインストールしてください。

yarn

yarn add vue-toasted

npm

npm install vue-toasted --save

CDN

index.html
<script src="https://unpkg.com/vue-toasted"></script>

実装方法

  1. vue-toastedを読み込む
main.js
import Vue from 'vue'
import App from './App.vue'
import Toasted from 'vue-toasted';

Vue.use(Toasted);

new Vue({
  render: h => h(App)
}).$mount('#app')

※CDNの場合はimportの記述は必要ありません。

2.Vueコンポーネントで呼び出す

App.vue
<template>
  <div id="app">
    <button @click="btnClick">クリック</button>
  </div>
</template>

<script>
export default {
  methods: {
    btnClick() {
      this.errToast("エラーです!");
    },
    errToast:function(msg){
      // main.jsで読み込んだので this.$toasted で呼び出せる
      this.$toasted.error(msg);
    },
  }
}
</script>

<style>
.toasted-container .toasted {
  /* スタイルを修正することもできます */
}
</style>

これで実装は完了です。
今回はエラーの表示の時のトーストを実装したため、赤色のトーストが表示されたと思います。
エラー以外にも、オプションを設定すれば様々なトーストを実装することができます。

応用編

オプション

index.js
import Vue from 'vue'
import App from './App.vue'
import Toasted from 'vue-toasted';

// オプション設定
// 今回はポジション・表示されてから消えるまでの時間・横幅についてのオプション追加
var options = {
  position: 'top-center',
  duration: 2000,
  fullWidth: true,
}

Vue.use(Toasted, options);

new Vue({
  render: h => h(App)
}).$mount('#app')

このようにオプションを渡してあげることによって、思い通りのトーストを簡単に実装することができます。
下記URLから、ぽちぽち操作して目で見ながら理想のトーストを作っていくこともできるのでおすすめです!
https://shakee93.github.io/vue-toasted/

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

2019年お世話になったJavascript(と一部Typescript)を振り返る

はじめに

Nuxt.js+Typescriptでの開発現場に入ってはや4ヶ月。前はマークアップエンジニアだったのでせいぜいjQueryでDOM操作することしか実務ではしていませんでした。
当たり前ですが今の現場ではゴリゴリとES2015+で書くので自分の出したPRや先輩のPRを眺めながら「このメソッドとか処理よく使ったなあ」というのを振り返ります。

また、あくまでも一現場のR&D的なプロダクトの中で書いている内容なので、そのまま皆さんのプロダクトに当てはまらないということは事前にお断りさせていただきます。

では早速いってみましょう!

アロー関数

わざわざ紹介するほどのことではないのですが、必ず使っているという意味で書きます!
もう一切functionって書かなくなりましたって話です。

function (arg1, arg2){
  return arg1 + arg2
}
(arg1, arg2) => {
  return arg1 + arg2
}

はい、functionはもう書かなくてOKです。厳密にいうと色々他にもあるのですが(thisの扱いとか)、簡単にかけるようになるのはいいですね。

これから下の処理ももちろんアロー関数で書いていきます。

map

渡ってくる配列の各要素に対して処理をします。返り値は新しい配列になります。

array1.map(処理) 

// 処理をした後のarray2が返ってくる

簡単なサンプルはこちら

const array1 = [1, 2, 3, 4];

const array2 = array1.map(x => x * 2);

console.log(array2); // [2 ,4, 6, 8]

map(x => x * 2) はfunctionを使って書くと

map(function(x) {
  return x * 2
})

と一緒です。

実際どんな感じで使ったりするかというと

arrayData.map(data => {
  return new Date(data.date).toLocaleDateString()
}

arrayDataが渡ってきて、その中身がObjectの配列だった時に、それぞれのdata(オブジェクト)のdate(日付)を抜き出してきて新しく配列を作り出す

という感じで使ったりしました。

reduce

配列の各要素に対して処理をします。返り値は単一の値です。(配列とは限らない)

array.reduce(処理)

// 何かの値が返ってくる(例えば計算結果など)

簡単な処理はこちら

const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;

// 1から4までを足す
console.log(array1.reduce((arg1, arg2) => {
  return arg1 + arg2
}));

// 合計値の10が返ってきます

実際のプロダクトでは

array.reduce((a,b) => {
  return a + b / WEEKDAYS_COUNT // WEEKDAYS_COUNTは1週間 = 7日という意味で7としていた
})

みたいな感じで処理をしたりしていました。

filter

配列に対してフィルタリングを行い、フィルタリングした後の値を新しい配列にして変更します

const words = ['deepleaning', 'python', ' javascript', 'php', 'html', 'css'];

const result = words.filter(word => word.length >= 6);

console.log(result);
// ["deepleaning", "python", "javascript"]という配列が返ってくる

実際のプロダクトでは

array.filter(array_data => {
  return array_data.program.weekday === targetWeekday
});

// array_data.weekday === targetWeekdayがtrueになるようなものだけを抽出して新しい配列を返す

のように使っていました。

Object.entries

Object.entries()は、引数に与えたオブジェクトが所有する、列挙可能なプロパティの組 [key, value] からなる配列を返します。オブジェクトに対して使い、配列を返す処理をしてくれるということです。
Objectで返ってくる内容を配列で取り出したい、加工したい、という時にたまに使っていました。

const object1 = {
  a: 'hogehoge',
  b: 10
};

console.log(Object.entries(object1));

// [["a", "hogehoge"],["b", 10]]

実際のプロダクトではmapと組み合わせて

Object.entries(obj).map(([key, value]) => ({[key]: value}))

/* こんな感じのオブジェクトを

obj = {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3"
}

*/

// こんな感じの返り値を出したい時に使います
/* 

[
  {
    "key1": "value1"
  },
  {
    "key2": "value2"
  },
  {
    "key3": "value3"
  }
]

*/

という感じで配列を作っていました。

Object.fromEntries

Object.fromEntries()は、key valueの組み合わせの配列をオブジェクトに変換します。

簡単なサンプルはこんな感じ

const array = [
  ['sample', 'data'],
  ['sample2', 100 ]
];

const obj = Object.fromEntries(array);

console.log(obj);
// { sample: "data", sample2: 100 }

配列からオブジェクトを生み出したい時に使っていました。

実際のプロダクトでは

Object.entriesの中でObject.fromEntriesをやっているところとかもあって、読み解くのに一苦労しました。笑

スプレッド構文

配列のようなオブジェクト(正確には for of で展開できるもの)を個々の値に展開するのがスプレッド演算子です。
記法としては... とピリオド3つを使って表現します。

配列の個々の値に展開する
const data = [1, 2, 3, 8, 11];

console.log(Math.max(...data)); // 11

実際のプロダクトでは

Math.max(
  ...this.dailyData.map(data => {
    return data.increaseCount;
  })
)

のようにして使っていました。

おまけ(Typescript)

enum

通常のJavascriptにはないもので、数値の定数に名前を付けて、その集合に対して定義ができるようにしたものです。
例えば

enum userStatus {
  ready,
  active,
  finished
}

のようにセットすると。Javascript側では

"use strict";
var userStatus;
(function (userStatus) {
    userStatus[userStatus["ready"] = 0] = "ready";
    userStatus[userStatus["active"] = 1] = "active";
    userStatus[userStatus["finished"] = 2] = "finished";
})(userStatus || (userStatus = {}));

のようにコンパイルされます。ちょっとややこしいですね。

実際に使う時にはこんな感じで使えるようになります。
(下記の場合だとVue.jsで使っています)

<template v-if="status === userStatus.ready">
  <!--userのstatusがreadyの時の内容-->
</template>

generics

genericsは「抽象化されたデータ型」を表現する機能です。
似た感じの関数だけど型が違うケースにおいて、引数である型を抽象的に使う時に便利です。

どういうことかといいますと

  • string型のデータを引数にしてstring型のvalueを持った配列を返す関数
  • number型のデータを引数にしてnumber型のvalueを持った配列を返す関数

これらの2つがあった時に

let stringArray = (valueOfString: string): string[] => {
  return [valueOfString, valueOfString, valueOfString];
};

let numberArray = (valueOfNumber: number): number[] => {
  return [valueOfNumber, valueOfNumber, valueOfNumber];
};

上記のように型をつけてもいいのですが、同じような形の関数を2つも書くのはめんどくさいし冗長ですよね。

なのでこれを

let genericsArray = <T>(genericsValue: T): T[] => {
  return [genericsValue, genericsValue, genericsValue]
}

とTとして抽象化することで

genericsArray<number>(10)
// [10,10,10]

genericsArrayの引数はnumber型で、かつ返り値もnumber型となってくれます。

<number>の箇所を
<string>にすれば

genericsArray<string>(10)

// ["10","10","10"]

のようにできます。

うーん、これは味がありますね。

最後に

実際の開発の中では他にも色々なメソッドに触れましたが、全部は書けないので、この辺で。

来年は業務で使っているJavascript、Typescript、Nuxt.js以外にもPythonを触っていく予定です。もっとたくさんコードを書いて周りのすごいエンジニアの人とちゃんと対等に仕事ができるように頑張ろう。

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

[ライブコーディング]JavaScriptでモンテカルロ法シミュレータを作る

モンテカルロ法とは

ルーレットの必勝法とか言われる掛け方の法則。
実際にカジノで実践するには紙とペンが必要になるので、それをWebアプリ化したかった

言い訳

事前準備のない、ガチのライブコーディングをやりたかった。

そして、
フレームワークとか、IDEだとか、ネーミングとか
一切気にせず
ただただ頭に浮かんだコードを書いて、htmlとjscssで楽しく遊ぶことを意識した

結果

とんでもないコードができあがった。

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>モンテカルロ法シミュレーター</title>
    <link rel="stylesheet" href="index.css">
  </head>
  <body>
    <h1>モンテカルロ法シミュレーター</h1>

    <div>
      ゲーム数: <span id="js-game"></span>
    </div>

    <div>
      数列: <span id="js-sequence"></span>
    </div>

    <div>
      bet: <span id="js-bet"></span>
    </div>

    <div>
      status: <span id="js-status"></span>
    </div>

    <div>
      ベット総額: <span id="js-total-bet"></span>
    </div>

    <div class="controller">
      <button id="js-win">勝った</button>
      <button id="js-lose">負けた</button>
    </div>
    <script src="index.js"></script>
  </body>
</html>

index.js
import $ from 'jquery';

// ゲームの回数
let game = 1;

// 数列
let sequence = [];

// ベット数
let bet = 0;

let status = 'はじまり'

const getSequence = (game) => {
    if (game === 1) {
        return [1, 2, 3];
    } else {
        return sequence;
    }
}

const getBet = () => {
    const first = sequence[0];
    const last = sequence[sequence.length -1];

    return first + last;
}

// 勝ったときの処理
$(document).on('click', '#js-win', () => {
    game++;
    sequence.pop();
    sequence.shift();

    // 終了かどうか
    if (sequence.length <= 1) {
        status = 'おわり'
    }

    // ゲーム数表示
    $('#js-game').html(game);

    // 数列表示
    sequence = getSequence(game);
    $('#js-sequence').html(sequence.join(','));

    // ベット表示
    bet = getBet();
    $('#js-bet').html(bet);

    // ステータス表示
    $('#js-status').html(status);


})

// 負けたときの処理
$(document).on('click', '#js-lose', () => {
    game++;
    sequence.push(bet);

    // ゲーム数表示
    $('#js-game').html(game);

    // 数列表示
    sequence = getSequence(game);
    $('#js-sequence').html(sequence.join(','));

    // ベット表示
    bet = getBet();
    $('#js-bet').html(bet);

    // ステータス表示
    $('#js-status').html(status);
})

// ゲーム数表示
$('#js-game').html(game);

// 数列表示
sequence = getSequence(game);
$('#js-sequence').html(sequence.join(','));

// ベット表示
bet = getBet();
$('#js-bet').html(bet);

// ステータス表示
$('#js-status').html(status);

感想

忘れかけていた何かを思い出した。

ライブコーディングの様子:
https://youtu.be/HJR_iUAMLWA

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

React Suspenseによって変わるデータ取得の世界観

はじめに

待ちに待った(?) Suspense for Data FetchingがExperimentalとして先行公開されました。

https://reactjs.org/docs/concurrent-mode-suspense.html

React.Suspense自体は以前からあり、Data Fetchingについてはどういうものになるか憶測していたのですが、その憶測を上回る状態になって驚きつつ期待もしています。

本投稿では思ったことを雑多に書いていきます。

Render-as-You-Fetch

React Suspenseは仕組み自体は簡単(promiseをthrowできるだけ)なのですが、それによってコーディングパターンは大きく変わります。それがRender-as-You-Fetchというパターンで紹介されています。Suspenseを使う上で、必ずしもこのパターンに従う必要はないのですが、このパターンは様々なメリットがあります。

Suspenseを利用するメリットとしてよく言われるのは、loading stateをまとめて扱えることです。これは、シンプルかつ明確なメリットであり、UXを簡単に改善することができます。また、Render-as-You-Fetchでは、データ取得のタイミングが早いため、アプリによってはUXに影響する改善が見られるかもしれません。

一方で、Render-as-You-FetchではDX改善もされることが期待されます。

もうuseEffectは使わない

React hooksが登場して、非同期処理特にデータ取得については、useEffectを使うことがベストプラクティスとして紹介されてきました。しかし、Suspense for Data Fetchingの登場により、それは過去の話になりそうです。Suspenseではデータ取得のためにuseEffectは使いません。

非同期処理にuseEffectを使うことは有効ではありますが、第二引数のdepsを正しく設定する必要があると言うのが悩ましい問題でありました。間違えると無限ループになったり、exhaustive-depsのルールに従う場合でも思い通りにならなかったりすることがありました。Suspenseではデータ取得はrenderの前に開始されるものであり、depsに頼ることはなくなります。useEffectの利用シーンはいわゆる副作用に限定され、より健全な形になるかもしれません。

react hooksからのthrow promise

Suspenseに対応する文脈で、react hooksでデータ取得を開始して同時にthrow promiseする手法がありますが、これはRender-as-You-Fetchではありません。renderが開始してからデータ取得しているからです。その手法自体に問題があるわけではありませんが、本質的にはSuspenseのパワーを部分的にしか使えていない形になります。ただ、実際は多くのケースでは、renderはそこまで遅くないので、UXには影響しない可能性があります。React.lazyと併用する場合以外は、Render-as-You-FetchによるUXの恩恵は少ないかもしれません。

Render-as-You-FetchによるDXのメリット

一言で言うと、fetchで取得したリモートデータとローカルのデータをほぼ区別することなくコーディングできるのです。コンポーネントのrenderにおいて、リモートデータがまだ取得中なのか取得済みなのかを区別する必要がなくなります。リモートデータは素直に外から与えられるpropsの一つになるか、内部で持つstateの一つになるだけです。

今までのデータ取得の考え方とは異なるので、全く新しいパターンとして認識する必要があります。

完全にシームレスにするにはProxyなどを使う必要があるのですが、そうでなくても、loading stateを無視できると言う点でリモートデータをローカルデータのように扱えるので、メリットがあります。

今後の展望

Render-as-You-Fetchは今後ベストプラクティスを確立していく段階だと思います。現状では、Relayが先行していますが、今後様々な提案がなされるのではないでしょうか。

私自身もこの新しいパターンに対応するライブラリを開発しています。

https://github.com/dai-shi/react-suspense-fetch

codesandboxで動作するリンクも用意していますので、興味ある方はいじってみてください。

おわりに

Suspense for Data Fetchingが来ると分かっている中、React hooksでデータ取得をするためのライブラリreact-hooks-asyncを開発していたのですが、あまりの変わり様に驚いています。いい意味で。react-hooks-asyncの活用の場もないことはないのですが、やはり今後はSuspenseの可能性を探求していきたいと思います。

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

JavaScriptで配列を結合

オブジェクトの配列を結合するメモ。

index.js
//deptsマスタ
const depts = [
    { deptNo: 1, deptName: '総務' },
    { deptNo: 2, deptName: '経理' },
    { deptNo: 3, deptName: '人事' },
];

//membersマスタ
const members = [
    { name: 'foo', deptNo: 1 },
    { name: 'boo', deptNo: 2 },
    { name: 'hoge', deptNo: 3 },
];


//ループしながら結合
const newMembers = members.map(item =>
    ({
        name: item.name,
        deptname: depts.find(value => value.deptNo === item.deptNo).deptName
    })
);

//配列の中身を表示
console.log(newMembers);

実行した結果は下記の通り。

node index.js

[
  { name: 'foo', deptName: '総務' },
  { name: 'boo', deptName: '経理' },
  { name: 'hoge', deptName: '人事' }
]

配列を作らず直接表示時に結合するなら、

members.map(item => {
    console.log(`name: ${item.name} deptName: ${depts.find(value=>value.deptNo === item.deptNo).deptName}`)
});

という感じでしょうか。

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

JSでDVDのロゴが動くやつ作ってみた

JSでDVDのロゴが動くやつを作りたくなったので作ってみました。

ezgif.com-video-to-gif.gif

デモ

dvd.pug
#container
  #box(style="width: 100px; height: 100px; background: red;") DVD
button#play-button 再生
dvd.scss
* {
  box-sizing: border-box;
}

body {
  margin: 0;
}

#container {
  width: 500px;
  height: 500px;
  background-color: black;

  #box {
    font-size: 44px;
    transition: transform 1s linear 0s;
  }
}
dvd.ts
// 色の文字列を格納した配列
const colors = ['red', 'blue', 'yellow', 'green'];

// 現在位置の方向
enum Direction {
  Top,
  Right,
  Bottom,
  Left
}

// boxのHTML要素
const box = document.getElementById("box");
// boxのwidth(数値)
const boxWidth: number = Number(box.style.width.replace('px', ''));

// containerのHTML要素
const container = document.getElementById("container");

// HTML要素の現在位置を取得する
function getCurrentPosition(element: HTMLElement) {
  return {
    top: element.getBoundingClientRect().top,
    right: element.getBoundingClientRect().right,
    bottom: element.getBoundingClientRect().bottom,
    left: element.getBoundingClientRect().left
  };
}

// boxの初期位置
const boxInitialPosition = getCurrentPosition(box);
// containerの位置
const containerPosition = getCurrentPosition(container);
// boxの現在位置
let boxCurrentPosition = boxInitialPosition;

// boxの左上の頂点が移動できるx座標の最小値
const minimumX = containerPosition.left;
// x座標の最大値
const maximumX = containerPosition.right - boxWidth;
// y座標の最小値
const minimumY = containerPosition.top;
// y座標の最大値
const maximumY = containerPosition.bottom - boxWidth;

// x座標の可動域(range of motion)の距離
const romX = maximumX - minimumX;
// y座標の可動域の長さ
const romY = maximumY - minimumY;

// boxを指定した絶対位置に移動させる
function changeTranslate(x: number, y: number): void {
  box.style.transform = `translate(${x - boxInitialPosition.left}px, ${y - boxInitialPosition.top}px)`;
}

// boxを現在位置から指定した距離だけ移動させる
function changeTranslateFromCurrent(x: number, y: number): void {
  box.style.transform = `translate(${getCurrentPosition(box).left + x - boxInitialPosition.left}px, ${getCurrentPosition(box).top + y - boxInitialPosition.top}px)`;
}

// 配列の要素をランダムに取り出す
function getRandomFromArr<T extends any[], K extends number>(arr: T): T[K] {
  return arr[Math.floor(Math.random() * arr.length)];
}

// ピタゴラスの定理
function pythagorasTheorem(x: number, y: number) {
  return Math.sqrt(x ** 2 + y ** 2);
}

// boxの移動する速さ[px/s]
const boxSpeedPerSecond = 200;

// 再生中か
let isPlaying = false;

// boxに付与する、transitionendイベント用のイベントハンドラ
function onTransitionEnd() {
  // boxの背景色を現在の色以外のランダムな色に変える
  const filteredColors = colors.filter(
    color => color !== box.style.backgroundColor
  );
  box.style.backgroundColor = getRandomFromArr(filteredColors);

  // 前回の位置
  const boxPrevPosition = boxCurrentPosition;
  // 現在位置
  boxCurrentPosition = getCurrentPosition(box);
  // 現在位置が上辺、右辺、下辺、左辺のどの方向にあるか
  const direction: Direction = (() => {
    switch (boxCurrentPosition.top) {
      case minimumY:
        return Direction.Top;
      case maximumY:
        return Direction.Bottom;
    }
    switch (boxCurrentPosition.left) {
      case minimumX:
        return Direction.Left;
      case maximumX:
        return Direction.Right;
    }
  })();
  // 前回の位置と現在位置の間の、x軸方向の距離とy軸方向の距離
  const prevToCurrentLength = {
    x: Math.abs(boxCurrentPosition.left - boxPrevPosition.left),
    y: Math.abs(boxCurrentPosition.top - boxPrevPosition.top)
  };
  // yに対するxの割合
  const XYrate = prevToCurrentLength.x / prevToCurrentLength.y;

  // 現在位置の方向によって場合分け
  if ([Direction.Top, Direction.Bottom].includes(direction)) {
    // 現在位置に対して向かい側に跳ね返るか
    const isBounceOppositeSide = (() => {
      if (boxCurrentPosition.left > boxPrevPosition.left) {
        // 左から右に移動した場合
        return boxCurrentPosition.left + romY * XYrate < maximumX;
      } else {
        // 右から左に移動した場合
        return minimumX < boxCurrentPosition.left - romY * XYrate;
      }
    })();

    // 現在位置と次回の位置の間の、x軸方向の距離とy軸方向の距離
    const currentToNextLength = (() => {
      if (isBounceOppositeSide) {
        const y = romY;
        return {
          x: y * XYrate,
          y
        };
      } else {
        const x =
          boxCurrentPosition.left > boxPrevPosition.left
            ? maximumX - boxCurrentPosition.left
            : boxCurrentPosition.left - minimumX;
        return {
          x,
          y: x / XYrate
        };
      }
    })();

    // 移動する時間を調整
    const currentToNextRLength = pythagorasTheorem(
      currentToNextLength.x,
      currentToNextLength.y
    );
    box.style.transitionDuration = `${currentToNextRLength /
      boxSpeedPerSecond}s`;

    // 計算した距離だけ移動させる
    changeTranslateFromCurrent(
      (boxCurrentPosition.left > boxPrevPosition.left ? 1 : -1) *
        currentToNextLength.x,
      (direction === Direction.Top ? 1 : -1) * currentToNextLength.y
    );
  } else {
    // 現在位置に対して向かい側に跳ね返るか
    const isBounceOppositeSide = (() => {
      if (boxCurrentPosition.top > boxPrevPosition.top) {
        // 上から下に移動した場合
        return boxCurrentPosition.top + romX / XYrate < maximumY;
      } else {
        // 下から上に移動した場合
        return minimumY < boxCurrentPosition.top - romX / XYrate;
      }
    })();

    // 現在位置と次回の位置の間の、x軸方向の距離とy軸方向の距離
    const currentToNextLength = (() => {
      if (isBounceOppositeSide) {
        const x = romX;
        return {
          x,
          y: x / XYrate
        };
      } else {
        const y =
          boxCurrentPosition.top > boxPrevPosition.top
            ? maximumY - boxCurrentPosition.top
            : boxCurrentPosition.top - minimumX;
        return {
          x: y * XYrate,
          y
        };
      }
    })();

    // 移動する時間を調整
    const currentToNextRLength = pythagorasTheorem(
      currentToNextLength.x,
      currentToNextLength.y
    );
    box.style.transitionDuration = `${currentToNextRLength /
      boxSpeedPerSecond}s`;

    // 計算した距離だけ移動させる
    changeTranslateFromCurrent(
      (direction === Direction.Left ? 1 : -1) * currentToNextLength.x,
      (boxCurrentPosition.top > boxPrevPosition.top ? 1 : -1) *
        currentToNextLength.y
    );
  }
}

// onTransitionEndイベントハンドラを付与
box.addEventListener("transitionend", onTransitionEnd);

const playButton = document.getElementById('play-button');
playButton.addEventListener("click", function() {
  if (!isPlaying) {
    isPlaying = true;
    playButton.innerText = "リセット";
    // onTransitionEndイベントハンドラを付与
    box.addEventListener("transitionend", onTransitionEnd);
    // x座標の最小値から最大値未満までの数字の連番が格納された配列
    const randomXArr = (() => {
      let arr: number[] = [];
      for (let i = minimumX; i < maximumX; i++) {
        arr.push(i);
      }
      return arr;
    })();
    // y座標の最小値から最大値未満までの数字の連番が格納された配列
    const randomYArr = (() => {
      let arr: number[] = [];
      for (let i = minimumY; i < maximumY; i++) {
        arr.push(i);
      }
      return arr;
    })();

    const random = Math.random();
    // 右辺に移動させるか、下辺に移動させるかをランダムに決める
    if (random < 0.5) {
      // x座標の最小値から最大値までの数字をランダムに取り出す
      const randomY = getRandomFromArr(randomYArr);
      // 斜辺の長さを求める
      const r = pythagorasTheorem(maximumX, randomY);
      // 移動する時間を調整
      box.style.transitionDuration = `${r / boxSpeedPerSecond}s`;
      // 右辺のランダムな位置に移動させる
      changeTranslate(maximumX, randomY);
    } else {
      // y座標の最小値から最大値までの数字をランダムに取り出す
      const randomX = getRandomFromArr(randomXArr);
      // 斜辺の長さを求める
      const r = pythagorasTheorem(randomX, maximumY);
      // 移動する時間を調整
      box.style.transitionDuration = `${r / boxSpeedPerSecond}s`;
      // 下辺のランダムな位置に移動させる
      changeTranslate(randomX, maximumY);
    }
  } else {
    isPlaying = false;
    playButton.innerText = "再生";
    // 色を赤に戻す
    box.style.background = 'red';
    // onTransitionEndイベントハンドラを削除
    box.removeEventListener("transitionend", onTransitionEnd);
    // トランジションをOFFにする
    box.style.transitionDuration = "0s";
    // 左上に移動させる
    changeTranslate(minimumX, minimumY);
    // 現在位置をリセット
    boxCurrentPosition = getCurrentPosition(box);
  }
});

ずっと見てられますね笑

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

Hey!そこの君! jQueryからSvelteへ乗り換えてみない?

はじめに

この記事はAteam Brides Inc. Advent Calendar 2019 24日目の記事です。
エイチームブライズ新卒1年目の@oekazumaです。
この記事はHey!そこの君! jQueryからSvelteへ乗り換えてみない?っていう感じのノリでサクサク書いていきます:bullettrain_front:

Svelteって?

こちらを見ていいねして帰ってきてください:blush:
君はVue,Reactの次に来るSvelteを知っているか?

当記事では jQueryで書いたらこうなるけどSvelteで書いたらこうなるよ!Svelteの輪を広げようよ!をコンセプトに解説していきます。

ボタンをクリックして文字を入れ替える

tv8k2-c2fbw.gif

jQueryの場合

jQuery
$(document).on('click', '#update', function() {
  $('#message').text('jQuery')
})

HTML
Hello <span id="message">World</span> !
<button id="update">change</button>

Svelteの場合

Svelte
<script>
    let message = 'World'
    function update() {
        message = 'Svelte'
    }
</script>

Hello { message }!
<button on:click={update}>change</button>

:person_with_blond_hair_tone1:<記述量的にはそんなに変わらない感あるけど書き方のスッキリ度合いが違うね!

文字入力をリアルタイム取得し表示する

qnkl8-b7f1g.gif

jQueryの場合

jQuery
$(function() {
  var $inputName = $('#input');
  var $outputName = $('#output');
  $inputName.on('input', function(event) {
    var value = $inputName.val();
    if (value) {
      $outputName.text(value);
    } else {
      $outputName.text('World');
    }
  });
});
<input id="input">
<p>Hello <span id="output">World</span>!</p>

Svelteの場合

<script>
    let name = '';
</script>

<input bind:value={name}>
<p>Hello {name || 'World'}!</p>

:santa:<svelteの書き方、気持ちがええっすね!これだけでほんまにええんか?ってレベルやん。

リストを追加と削除ができるようにする

8z858-tvvob.gif

jQueryの場合

jQuery
(function() {
  var list = [1, 2 ,3]

  $(document).on('click', '#add', function() {
    addItem(list.length+1)
    list.push(list.length+1)
  })
  $(document).on('click', '.remove', function(event) {
    $(event.target).parent().remove()
    updateLength()
    list = list.slice(0 ,list.length-1)
  })

  function init() {
    for (var i = 0; i < list.length; i++) {
      addItem(list[i])
    }
  }

  function addItem(name) {
    $('#list').append('<li>' + name + ' <button class="remove">remove</button></li>')
    updateLength()
  }

  function updateLength() {
    $('#length').text($('#list li').length)
  }

  init()
})()
HTML
<p>Length: <span id="length">0</span></p>
<ul id="list"></ul>
<button id="add">add</button>

Svelteの場合

Svelte
<script>
let list = [1, 2, 3]

function addItem() {
  list = [...list, list.length + 1]
}

function removeItem() {
  list = list.splice(0, list.length - 1)
}
</script>

<p>Length: { list.length }</p>
<ul>
  {#each list as item}
    <li> { item }
      <button on:click={removeItem}>remove</button>
    </li>
  {/each}
</ul>
<button on:click={addItem}>add</button>

:man_tone1:<Svelteはpush()とかsplice()での配列操作では画面の自動更新されないから若干違和感あるけどjQueryと比べたらすごくスッキリで気持ちいい!!

さいごに

Svelte素晴らしくないですか?

:guardsman_tone1:<うんうん素晴らしいね!

jQueryを使っている君はSvelteに乗り換えたくなった?

:joy_cat:<泣くほど変えたくなったよ!

っというわけで明日はクリスマス:santa_tone2: Ateam Brides Inc. Advent Calendar 2019 ラストを @okoshiが飾ってくれます!
楽しみにしてください!

私たちのチームで働きませんか?

alt
エイチームは、インターネットを使った多様な技術を駆使し、幅広いビジネスの領域に挑戦し続ける名古屋の総合IT企業です。
そのグループ会社である株式会社エイチームブライズでは、一緒に働く仲間を募集しています!

上記求人をご覧いただき、少しでも興味を持っていただけた方は、まずはチャットでざっくばらんに話をしましょう。
技術的な話だけでなく、私たちが大切にしていることや、お任せしたいお仕事についてなどを詳しくお伝えいたします!

Qiita Jobsよりメッセージお待ちしております!

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

PlayCanvasでDOMを扱う手引き [もっと装飾つけちゃお編]

前回はstyleを付与してみました

装飾しちゃおと言った割にはそんな装飾されてるのか…?という

positionしか説明できませんでしたから、ここではCSSのプロパティの解説を行なっていきます。

CSSについて

CSSは基本HTMLとセットで使われる言語で、ウェブページのスタイルを指定する役目を持っています。
作成された文書のスタイルを指定する技術のことを全般的にスタイルシートと言って、スタイルシート言語の一つがCSSです。

難しいことは考えず、フォントの色とかサイズとか、背景の色を変えたりとかできるものと思えば良いでしょう。

前回で使用したCSSのstyleがあるので、
これを基に装飾をつけていきます。

装飾しましょう

前回はpositionとbackgroundだけ使いました。

.content {
    position: absolute;
    top: 0;
    left: 0;
    width: 500px;
    height: 500px;
    background: #ffffff;
}

例えば背景を変えたいときはbackgroundを変更すれば良いです。

background

backgroundは16進数のカラーコードで指定することができます。
カラーコードについてよくわからない人はググってみてね。

カラーコード以外にもredblueyellowなど英単語も対応しているのがあります。
使用しているブラウザがchromeを使っているなら以下のように設定もできます。

カラーコードを視覚的に設定したり
スクリーンショット 2019-12-23 17.09.19.png
英単語で指定できます
スクリーンショット 2019-12-23 17.09.33.png

backgroundは他にも背景画像を指定することもできます。
今回のPlayCanvasでは背景画像を使うというケースは少ないと思うので掘り下げませんが、ぜひリファレンスで知っておくと良いと思います。

また、背景色だけ変えるのであれば、background-color: #ffffff;-colorと指名してあげるとなお親切です。

.content {
    position: absolute;
    top: 0;
    left: 0;
    width: 500px;
    height: 500px;
    background-color: #ffffff;
}

border

背景だけではなんか物足りないから縁にも装飾をつけたい時には、borderです。
DOMの要素の周りに線をひくことができて、線幅や色も変えれます。

.content {
    position: absolute;
    top: 0;
    left: 0;
    width: 500px;
    height: 500px;
    border: 5px solid #000000;
    background-color: #ffffff;
}

スクリーンショット 2019-12-23 17.16.19.png

線のスタイルも変えることができて、soliddasheddotteddoubleなど、実線や破線、点線もあるで使い分けてみましょう。
スクリーンショット 2019-12-23 17.23.23.png

角を丸くする

borderのプロパティで角を丸くすることができます。
border-radiusを使います。4つ角をまとめて指定できますが、値を一つだけつけるだけでも十分です。

例えば以下のように指定することもできますが、必要に応じて使っていきましょう。
border-radius: 100px 25px 50px 50px / 50px 25px 50px 25px;
スクリーンショット 2019-12-23 17.28.19.png
引用:border-radius-CSS3リファレンス

.content {
    position: absolute;
    top: 0;
    left: 0;
    width: 500px;
    height: 500px;
    border: 5px solid #000000;
    border-radius: 25px;
    background-color: #ffffff;
}

スクリーンショット 2019-12-23 17.25.11.png

完全に丸にすることもできます。
スクリーンショット 2019-12-23 17.30.12.png

margin, padding

余白つけたいときなんかに使うものですが、これは以前記事にもしました

例えば今回のプロジェクトに少し追記してみましょう。

var AddHtml = pc.createScript('addHtml');

AddHtml.attributes.add("stylesheet",{type:"asset",assetType:"css"});

AddHtml.prototype.initialize = function() {
    var domContent = document.createElement("div");
    domContent.classList.add("content");
    var child1Content = document.createElement("div");
    child1Content.classList.add("child1");
    var child2Content = document.createElement("div");
    child2Content.classList.add("child2");

    document.body.appendChild(domContent);
    domContent.appendChild(child1Content);
    domContent.appendChild(child2Content);

    var link = document.createElement('link');
    link.rel = "stylesheet";

    link.href = this.stylesheet.getFileUrl();

    document.head.appendChild(link);
};
.content {
    position: absolute;
    top: 0;
    left: 0;
    width: 500px;
    height: 500px;
    padding: 20px;
    border: 5px solid #000000;
    border-radius: 25px;
    background-color: #ffffff;
}

.content .child1 {
    width: 100px;
    height: 100px;
    border-radius: 50%;
    background-color: #cccccc;
    margin: 10px 20px;
}

.content .child2 {
    width: 100px;
    height: 100px;
    border-radius: 50%;
    background-color: #333333;
    margin: 10px 20px;
}

contentに子要素を追加して、反映してみるとこんな感じ
スクリーンショット 2019-12-23 17.50.53.png

余白がついてるのがわかりますね。

margin、paddingはどちらも余白をつけるものですが、明確に使い分ける必要もありません。
しかし、私もweb屋の端くれですので、ちゃんと使い分けるようにましょう。

marginは要素と隣り合わせの要素との余白を作るために使用
paddingは要素の子要素との余白を作るために使用

と上記2点で分けるようにすると良いと思います。

フォント(テキスト)

先ほどまで四角い要素ばかりで、テキストのことを全く話していませんでしたね。
Webにとって主役と言っても過言ではないテキストですからね。

font

フォントは大体以下のような指定ができます。

font …… フォントに関する指定をまとめて行う
font-style …… フォントをイタリック体・斜体にする
font-variant …… フォントをスモールキャップにする
font-weight …… フォントの太さを指定する
font-size …… フォントのサイズを指定する
font-family …… フォントの種類を指定する
font-size-adjust …… フォントのサイズを調整する
font-stretch …… フォントを縦長・横長にする

引用:font-スタイルシートリファレンス

よく使うのはfont-size, font-family, font-weightぐらいでしょうか。
イタリック体などを使うときは、font-style: italic;と指定すれば良いでしょうし、
太字の場合は、font-weight: bold;と指定できます。

color

このcolorと見て、最初背景の色を変えるものとかオブジェクト自体のカラーを変えるものとか思った人は少なくないでしょう。
CSSでのcolorはフォント、テキストのカラーを指定できます。
ここでも勿論ですが、16進数のカラーコードを使って指定します。
多分、テキストが主役であるから、font-colorとかいうプロパティは作らず、colorだけにしているんじゃないかなと思ってます。

では、プロジェクトの方でもテキストを入れてみましょう。

jsでp要素を作ってテキストを入れ込んでいきます。
ここでは、innerHTMLを使ってテキストを追加しています。

ここで、div要素ではなくp要素を使った理由ですが、
基本テキストを使う場合は、p要素かspan要素を使うからです。
例外として、リンクとしての意味を持つテキストはa要素で囲ったり、リストとして使う場合はli要素を使うこともありますが、
特に意味のないテキストの要素には、pとspanを使うので覚えておきましょう。

var AddHtml = pc.createScript('addHtml');

AddHtml.attributes.add("stylesheet",{type:"asset",assetType:"css"});

AddHtml.prototype.initialize = function() {
    var domContent = document.createElement("div");
    domContent.classList.add("content");
    var child1Content = document.createElement("div");
    child1Content.classList.add("child1");
    var child2Content = document.createElement("div");
    child2Content.classList.add("child2");

    var child1Text = document.createElement("p");
    child1Text.innerHTML = "おとな";
    child1Content.appendChild(child1Text);
    var child2Text = document.createElement("p");
    child2Text.innerHTML = "こども";
    child2Content.appendChild(child2Text);

    document.body.appendChild(domContent);
    domContent.appendChild(child1Content);
    domContent.appendChild(child2Content);

    var link = document.createElement('link');
    link.rel = "stylesheet";

    link.href = this.stylesheet.getFileUrl();

    document.head.appendChild(link);
};

前もって説明を失念してましたが、cssのスタイルを適用するためにはclass名や要素名を指定しなくてはいけません。
例えば、.content .child1.contentの中の.child1にスタイルを当てますよ。という意味。
なので、同じクラス名を与えたとしても、親要素のclass名も指定しスタイルをつけることで、他の同じclass名の要素にはこのスタイルは当てないと言ったこともできます。
これをセレクタと言いまして、色んな種類がありますので、ぜひリファレンスをみてください。
セレクタの種類-CSSの基本
ちなみに、hoverのアニメーションなどもこのセレクタで指定することができるのでお試しを。

.content {
    position: absolute;
    top: 0;
    left: 0;
    width: 500px;
    height: 500px;
    padding: 20px;
    border: 5px solid #000000;
    border-radius: 25px;
    background-color: #ffffff;
}

.content .child1 {
    width: 100px;
    height: 100px;
    border-radius: 50%;
    background-color: #cccccc;
    margin: 10px 20px;
}

.content .child2 {
    width: 100px;
    height: 100px;
    border-radius: 50%;
    background-color: #333333;
    margin: 10px 20px;
}

.content p {
    color: #ff0000;
    font-size: 16px;
    font-weight: bold;
}

すると、以下のような感じに。
スクリーンショット 2019-12-23 18.37.16.png

テキストの色んな指定

テキストには色んなプロパティがあって色んな指定をすることができます。
例えば、今はテキストが左寄せになっていますね。
これを中央揃えにしたい時には、text-alignを使います。

.content p {
    color: #ff0000;
    font-size: 16px;
    font-weight: bold;
    text-align: center;
}

以下のようにテキストが中央寄せになりましたね。
他にも右寄せなどもできるのでリファレンスで確認してみましょう。
スクリーンショット 2019-12-23 18.49.38.png

他にも行の高さはline-height、文字の間隔はletter-spacingを使ったりします。
複数行や文字の余白を作りたいときなんかに使用してみましょう。

.content .child1 {
    width: 100px;
    height: 100px;
    padding: 20px 0;
    box-sizing: border-box;
    border-radius: 50%;
    background-color: #cccccc;
    margin: 10px 20px;
}

.content .child2 {
    width: 100px;
    height: 100px;
    padding: 20px 0;
    box-sizing: border-box;
    border-radius: 50%;
    background-color: #333333;
    margin: 10px 20px;
}

.content p {
    color: #ff0000;
    font-size: 16px;
    font-weight: bold;
    text-align: center;
    line-height: 1.5;
    letter-spacing: 0.05em;
}

ついでに上下の余白も調整してみました。
line-heightにpxといった単位がありませんが、単位をつけずに数値のみを指定すると、その数値にフォントのサイズを掛けた値が行の高さとなります。
なので、ここでは16px*1.5となり、24pxになります。
また、letter-spacingもpxではなく、emという見慣れない単位がありますね。
これはこの要素のfont-sizeの値にかけた値になります。先ほどのline-heightと同じですね。
このemという単位はpxを使うプロパティ全てで使うことができるので、font-sizeごとに変えたいときなんかに利用できます。

スクリーンショット 2019-12-23 18.58.29.png

display

最後にこれだけ説明してこの記事は一旦終わりにします!

今は子要素が縦に並んでしまっていますが、横並びにしたいとき。
そんなときはdisplayを変更してあげます。

.content .child1 {
    display: inline-block;
    width: 100px;
    height: 100px;
    padding: 20px 0;
    box-sizing: border-box;
    border-radius: 50%;
    background-color: #cccccc;
    margin: 10px 20px;
}

.content .child2 {
    display: inline-block;
    width: 100px;
    height: 100px;
    padding: 20px 0;
    box-sizing: border-box;
    border-radius: 50%;
    background-color: #333333;
    margin: 10px 20px;
}

displayプロパティは、ブロックレベル・インライン・テーブル・ルビ・フレックスコンテナ等の、要素の表示形式を指定する際に使用します。

引用:display-スタイルシートリファレンス
リファレンスの言葉をそのまま引用してみましたが、なんのことやらと思います。

ここで覚えて欲しいのはブロックレベルとインラインだけで大丈夫です。
ブロックレベル、すなわちブロック要素のことですが、これは見出しやリストなどの一つのまとまった単位で
表される要素です。このブロック要素には改行が前後につきます。
異なってインライン要素には、前後に改行がありません。

どういうことかというと、このdisplayを変更する前は縦に積まれていました。
これはこの要素がブロック要素で、前後に改行が入っていたために縦に積まれてしまっていました。
これをinline-blockに変えることで、インライン要素にして横並びにしました。
これなら、inline-blockではなくてもinlineでもいいんじゃないかと思うかもしれません。
しかし、インライン要素はwidth, heightで横幅高さを指定することができないんですね。
横幅高さを指定できるのはブロック要素なのでinline-blockを使うことで、
インライン要素のコンテナを作り、かつ内部ではブロック要素の要素を作るのです。

スクリーンショット 2019-12-23 19.08.31.png

横並びにするのはinline-blockだけではなく、flexなどもあります。
今のCSSではflexを使った方が良いと思いますが、私個人的には場合によって使い分けるのが良いと思ってます。
flexについてはMDNリファレンスから参照しましょう


これでおおよそのことができるようになったのではないでしょうか。

CSSは他にもhoverアニメーションを作ったり、a要素じゃなくてもcursor: pointer;を使うことでカーソルのポインターを変えることもできます。
探せば探すほど、これこれ!っていうのが見つかると思いますので、ぜひ色んなプロパティを探してみてくださいね!

次回は、PlayCanvasのプロジェクトをZIPでダウンロードしてからDOM要素と連携させてみる話です。

お楽しみに!

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

Laravel Mixで出力されたJavaScriptファイルのパッケージ構成比をsource-map-explorerで調べる

Laravel Mixで出力されたJavaScriptファイルのファイルサイズが大きくて困っているとき、
まずはどのパッケージがどれぐらいのサイズを占めているか調べると思います。
そこでどうやって構成比を調べるかを「webpack バンドル サイズ」などで検索したところ、source-map-explorerというパッケージを見つけました。

https://github.com/danvk/source-map-explorer

Getting started

サクッと使ってみましょう。
まず用意するのはLaravelアプリで、最新の6を落としてきてnpm installまで済ませたところから始めます。
次に今回の肝であるsource-map-explorerをインストールします。

$ npm install --save-dev source-map-explorer

package.jsonanalyzeコマンドを追加しましょう。

{
    ...
    "scripts": {
        ...

        "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "analyze": "source-map-explorer 'public/js/*.js'"
    },
    ...
}

そしてJavaScriptファイルを出力した後にanalyzeを実行します。

$ npm run dev
$ npm run analyze

> @ analyze
> source-map-explorer 'public/js/*.js'

public/js/app.js
  Unable to find a source map.
  See https://github.com/danvk/source-map-explorer/blob/master/README.md#generating-source-maps

ソースマップがないと怒られました:cry:

webpack.mix.jsを修正します。

mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css')
    .sourceMaps();

npm run productionでないとソースマップが出力されません。

$ npm run production
$ npm run analyze

スクリーンショット 2019-12-23 18.21.11.png

実行するとブラウザで上記のような画面が表示されます。
要素をクリックするとさらに詳しく調べられたりします。
シンプルでわかりやすいですね:beer:

参考URL

https://qiita.com/qrusadorz/items/cd05ffe3ad08754b9e65
https://create-react-app.dev/docs/analyzing-the-bundle-size
https://github.com/danvk/source-map-explorer

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

Promise使った非同期処理入門

JavaScriptの非同期処理

前提としてJavaScriptはシングルスレッドなので、並行処理(2つの処理を同時作業)はできないです。
ですが、XHR(サーバーと通信を行う処理)等で処理をサーバーに移譲し
「JavaScript以外に処理を任せた場合」、並行して処理を行う事が可能です。
※JavaScriptのみで非同期風に処理を行う方法もあります。

今回紹介する、Promiseは並行処理していたプロセスの完了後に行いたい処理が出てきた場合に活躍するオブジェクトです。

料理でいうと、カレーを作る場合、ルーの作成と、ご飯を炊く作業は同時並行で進める事ができます。
ただ最終的に、炊きあがったご飯に、ルーをかけてカレーを提供する際には、ルーもご飯も出来上がってる必要がありますよね!

ただ、作る人や環境によってルーを作るスピードやお米の炊き時間が違うように、
サーバーのスペックや状態によってプロセスの完了タイミングは違ってきます。
この完了のタイミング確実に待ってから、次の処理を行いたいときに大活躍なのが、Promise先生です!

STEP1 Promiseオブジェクトの生成

Promiseをnew演算子で呼び出し、Promiseオブジェクトを作成する事で使用する事ができます。
Promiseオブジェクトは非同期の作業を開始して、完了した際に resolve 関数 (成功した場合)
もしくは reject 関数 (エラーが発生した場合) のいずれか一方を呼び出します。
下記コードは例です。

const url = "https://dummynourldayozukki/gets/"

const promise = new Promise(function(resolve, reject) {
  // ここに非同期処理を書く(^o^)
  const request = new XHTMLRequest();
  request.open('GET', url, true);

  request.onload = () => {
    // 成功した場合
    resolve('成功');
  };

  request.onerror = () => {
    // 失敗した場合
    reject(new Error('エラー'));
    return;
  }
});

STEP2 thenメソッドを使う

thenメソッドは、Promiseオブジェクトの中の非同期処理がエラーなく「完了」した際に呼ばれます。
resolveメソッドが呼ばれたときです。

Promiseオブジェクトのthenメソッドが呼ばれます。
下記の例のresponseには、resolveで設定した値が呼ばれます。

const url = "https://dummynourldayozukki/gets/"

const promise = new Promise(function(resolve, reject) {
  // ここに非同期処理を書く(^o^)
  const request = new XHTMLRequest();
  request.open('GET', url, true);

  request.onload = () => {
    // 成功した場合
    resolve('成功');
  };

  request.onerror = () => {
    // 失敗した場合
    reject(new Error('エラー'));
    return;
  }
});

// ↓↓↓↓↓↓↓↓追記↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

promise.then(function (response) {
  console.log(response); // '成功'
});

STEP3 catchメソッドを使う

処理にエラーが発生した場合に、catchメソッドが呼ばれます。
正しく取得できなかった時はこちらに処理を書きます。

const url = "https://dummynourldayozukki/gets/"

const promise = new Promise(function(resolve, reject) {
  // ここに非同期処理を書く(^o^)
  const request = new XHTMLRequest();
  request.open('GET', url, true);

  request.onload = () => {
    // 成功した場合
    resolve('成功');
  };

  request.onerror = () => {
    // 失敗した場合
    reject(new Error('エラー'));
    return;
  }

  // ↓↓↓↓↓↓↓↓追記↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

  promise
  .then(function (response) {
    console.log(response); // '成功'
  })
  .catch(function (err) {
    console.log(err.message); // エラー'
  });
});

最後に

今回は、Promiseの基礎的な使い方を説明しました。
Promiseを使うと非同期処理の呼び出しと、
その後の処理がthenやcatchによって境界ができコードが分かりやすくなります。

今回紹介できませんでしたが、
Promise.allや、Promise.raceを使うことで複数の非同期処理の成功タイミングで、処理を行う記述もできます。

では、良いJavaScriptライフを!!

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

Javascriptレシピ⑥

Javascriptの簡単なメモです。
アプリ制作に応用できる基本レシピですので、参考にされたい方はどうぞ。

アロー関数

const wanko = function(a){
    return r+r;
};

//アロー関数を使うと

const wanko = r => {
    return r+r;
};

//関数が一行の時は

const wanko = r => r+r;

プログレスバー

<progress id='progress' value='0' max='100'>0%</progress>
<inoput type='button' id='button' onClick='func1()' value='スタート'>

let val;
let intervaiID;

function func1() {
    val= 0;
    document.getElementById(button).disabled = true;
    intervalID = setInterval('update()', 50);
}

function update(){
    val += 1;
    document.getElementById('progress').value = val; 
    document.getElementById('progress').innerText = val + '%';

    if(val = 100){
        clearInterval(intervalID);
        document.getElementBiId('button').disabled =false;
    }
}

日時比較

Dateオブジェクトを使用します。
new Date()でインスタンス化して、インスタンスをもとに計算していきます。

let startTime = new Date();
let endTime = new Date();
let elapsedtTime = endTime.getTime() - startTime.getTime();

let mins = elapsedTime / (1000 * 60);
let secs = elapsedTime / 1000;
Math.floor(mins);
Math.floor(sexs);

無名関数と即時関数

//普通の関数
function sum(a,b){
    const result = a+b;
    return result;
}
const answer = sum(1,2);
console.log(answer);

//無名関数
const sum = (a,b) =>{
    const result = a+b;
    return result;
}
const answer = sum(1,2);

無名関数は関数を変数に入れることが出来ます。
変数に関数を格納できるというのが無名関数のメリットですね。

即時関数とは定義するとすぐに実行される関数です。

const sum = ((a,b) => {
    const result = a+b;
    return result;
})(1,2);
comsole.log(sum);

プログラムがスッキリしています。

DOM操作

Windowオブジェクトはブラウザが持つ情報をすべて持っています。
DocumentオブジェクトはアクセスしているWebサイトそのものでHTMLなどを保持しています。

DOMとはDocument Objrct Modelで、Documentオブジェクトを通じて個々の要素を取得操作することが出来ます。

WindowオブジェクトはHTMLをdocumentオブジェクトに変換する時にツリー構造という構造で保持します。

DOMの動作はdocumentオブジェクトのgetElementByIdメソッドを使います。



const p = document.createElement('p');
const text = document.createTextNode('要素3');
document.body.appendChild(p).appendChild(text);

JSはブラウザ上で動作しているのでブラウザ上で組み立てられて表示されるHTMLの変更ができます。

Arrayオブジェクト

const family = new Array('pome','toypoo','siba');

//長さ
console.log(family.length);

//sliceでは0番目と2番めの一つ前の1番目が指定されます。
console.log(family.slice(0,2));

//reverseでは配列の要素の順番をひっくり返せます。
console.log(family.reverse());

//pushでは配列の末尾に新しい要素を追加し、追加後の要素数を取得できます。
console.log(family.push('a','b'));

//5

//popでは末尾の要素を削除します。
const last = family.pop();

//unshiftでは先頭に要素を追加します。
const start = family.unshift('a');

//indexOfは要素のインデックスを取得します
const wan = family.indexOf('pome');

//spliceはインデックスの位置を指定して要素を削除します。
const remove = family.splice(pos,1);

//任意の箇所を削除する
delete family[1];

//多次元配列
const familybig = family[1]; 

Mathオブジェクト

//円周率
console.log(Math.PI);

//√計算
comnsole.log(Math.sqrt(64));

//最大値取得
Console.log(Math.max(1,2,3,4,5));

//四捨五入した値を取得
console.log(Math.round(3434.434343));

Mathオブジェクトはstaticなオブジェクトのためnewしなくても使えます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactアプリケーションにSentryを導入してみる(Webpack使用)

React Native版の記事はこちらです。

SentryをReactアプリケーションに導入してみます。前半はReactに限らず、ブラウザ上で動かすJSに共通する設定になります。

設定に必要な基本的な情報はすでに取得している状態を想定して進めていきます。
最低限の使用の場合は、@sentry/browserをインストールし、以下のコードをJSの最上位ファイルに追加するだけです。

$ npm install @sentry/browser
App.js
import * as Sentry from '@sentry/browser';

Sentry.init({
  dsn: "ここにプロジェクトのDSN"
});

throw new Error('error'); // 適当にエラーをスローしてみる

Error__error.png

ローカルで動かしてみると、問題なくSentryのissuesの中にエラーイベントが入りました。

このままでも使えるのですが、ソースマップファイルやリリース情報をSentryに送信してログをより見やすくするために、Webpackの設定を追加してみます。

公式のドキュメントに従いますが、必要のない設定項目は割愛しています。
まずは.sentryclircというファイルを追加して、Webpackでビルドする時にSentryのプロジェクトにアクセスできるようにします。

.sentryclirc
[auth]
token=Authトークン
[defaults]
org=組織名
project=プロジェクト名

公式のWebpackプラグインをインストールして、

$ npm install --save-dev @sentry/webpack-plugin
webpack.config.js
const SentryWebpackPlugin = require('@sentry/webpack-plugin');

...
plugins: [
  new SentryWebpackPlugin({
    include: "build/static/js"
  }),
  ...
]
...

Webpackのpluginsに追加します。ここでincludeに、ソースマップファイルを送信してほしいJSのディレクトリを指定します。
デフォルトではディレクトリ内のnode_modules以外の全てのJS/CSSファイルが送信されます。

そのほかのオプションはこちら
オプション一覧の最初の行を確認してください。このオプションにrelease文字列を渡せば任意のリリース情報を設定できますが、デフォルトではgitのコミットIDがそのままリリース情報として使用されるようです。

この段階でもしgitの設定をしていなければ、git initからaddcommitまで一通りしてから、Webpackでビルドしてみます。

Creating an optimized production build...
> Analyzing 6 sources
> Rewriting sources
> Adding source map references
> Bundled 6 files for upload
> Uploaded release files to Sentry
> File upload complete

Source Map Upload Report
  Minified Scripts
    ~/2.0f707d42.chunk.js (sourcemap at 2.0f707d42.chunk.js.map)
    ~/main.663c8045.chunk.js (sourcemap at main.663c8045.chunk.js.map)
    ~/runtime-main.d5ffe654.js (sourcemap at runtime-main.d5ffe654.js.map)
  Source Maps
    ~/2.0f707d42.chunk.js.map
    ~/main.663c8045.chunk.js.map
    ~/runtime-main.d5ffe654.js.map
Compiled with warnings.

こんな感じでコンソールにソースマップの情報が表示されます。
あとはサーバーに何かしらサーバーにデプロイしてみてください。これでSentryの準備は完了です。

デプロイしたページを開いて、エラーを発生させてみると、

Error__error.png

まず、RELEASE欄にちゃんとコミットIDが入っているのがわかります。

Error__error.png

ソースマップが送信できているので、エラーの発生箇所も確認できます。

ReactのError Boundaryでレポートダイアログを使用してみる

Sentryには、エラーが発生した時にユーザーからフィードバックを得るためのフォームを表示する機能が用意されています。

User Feedback
https://docs.sentry.io/enriching-error-data/user-feedback/?platform=javascript

これをReactのError Boundaryで使用する例がドキュメントに載っていたので、試してみました。
https://docs.sentry.io/platforms/javascript/react/#error-boundaries

SentryBoundary.js
import React, { Component } from 'react';
import * as Sentry from '@sentry/browser';

class SentryBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { eventId: null };
  }

  // エラーが発生した時にstateを更新する
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  // エラーが発生した時にSentryのscope(エラーの情報をまとめるもの)を作成
  componentDidCatch(error, errorInfo) {
    Sentry.withScope((scope) => {
      scope.setExtras(errorInfo); // エラーの情報をセット
      const eventId = Sentry.captureException(error); // Sentryにエラーを送信し、IDを取得
      this.setState({ eventId }); // エラーIDをstateにセット
    });
  }

  render() {
    if (this.state.hasError) { // エラーが発生したら画面にボタンを表示する
      return (
        <button
          onClick={() => Sentry.showReportDialog({ eventId: this.state.eventId })} // Sentryのダイアログを表示
        >
          エラーを報告する
        </button>
      );
    }

    // 何もない場合はそのまま子要素を表示
    return this.props.children;
  }
}

export default SentryBoundary

Error Boundary自体はReactの機能(実装パターン)です。

App.js
<SentryBoundary>
  <ChildComponent />
</SentryBoundary>

使用する単位は任意ですが、このように子要素をError Boundaryで囲っておいて、子要素のレンダリングで何かしらエラーが発生した時に代りに別の要素を表示したりすることができます。

この例では、エラーが発生した時に「エラーを報告する」ボタンを表示し、ボタンが押されたらSentryのダイアログを表示するようにしています。

実際にError Boundaryの子要素でエラーが発生するようにしてみます。

例えば下記のように、Reactのレンダリングとは関係ないところでエラーを発生させてもError Boundaryはcatchしません。

ChildComponent.js
import React from 'react';

function ChildComponent() {
  window.setTimeout(() => {
    throw new Error('error test');
  }, 1000);
  return (
    <div>
      エラーを発生させます...
    </div>
  );
}

export default ChildComponent;

このように、Reactのレンダリングが不可能になるエラーが発生した場合にError Boundaryがエラーをcatchします。
(例えば、returnするJSXの中で存在しないfunctionを叩く)

ChildComponent.js
import React from 'react';

function ChildComponent() {
  return (
    <div>
      エラーを発生させます...
      {window.foo()}
    </div>
  );
}

export default ChildComponent;

React_App_と_sentry-test____works_tamaki_sentry-test__-_____src_ChildComponent_js__sentry-test_.png
起動してみると、「エラーを発生させます...」という文言の代わりにボタンが表示されました。

これを押すと、このようなSentryのダイアログが表示されます。
React_App.png

名前とメールアドレス、詳細を入力して送信してみると...

Sentryのエラー詳細の方にUser Feedbackがちゃんと追加されました。
TypeError__window_foo_is_not_a_function.png

ダイアログの文言なども変更できるようです。
https://docs.sentry.io/enriching-error-data/user-feedback/?platform=javascript#customizing-the-widget

言語は自動的にブラウザの言語から設定されるようですが、一番大きなタイトルが英語のままだったので、オプションを渡してみます。ユーザーの名前・メールアドレスは最初から指定すれば最初から入力しておけるようです。

SentryBoundary.js
...
        <button
          onClick={() => Sentry.showReportDialog({
            eventId: this.state.eventId,
            title: "すみません、問題が発生しました",
            user: {
              email: "test@test.com",
              name: "React 和子"
            }
          })}
        >
          エラーを報告する
        </button>
...

React_App.png

問題なく設定できました。

React_App.png

ダイアログにはclassがセットされているので、見た目のカスタマイズもCSSで簡単にできそうです。

以上、ReactNativeとWebでSentryを触ってみました。特に嵌るところもなく、スムーズに導入できました。

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

Slickで `centerMode : true` の時 `slidesToScroll` のオプションは無視される

Slickの CenterMode + SlideToScrollでスライダーを動かすと、Slickの仕様でSlideToScrollが無視されてしまいます。
(真ん中寄りで3枚ずつ動かすとかです)

See the Pen slider 1 by Sota Takahashi (@Sota) on CodePen.

$(document).ready(function(){
  const target = $('.slider_box')
  target.slick({
  slidesToShow: 3,
  slidesToScroll: 3,
  dots: false,
  centerMode: true,
  prevArrow: '<button class="prev arrow">←</button>',
  nextArrow: '<button class="next arrow">→</button>'
  });
});

GitHubIssueで2016/05に上がっており、
解決するためにプルリク投げてる方はいるようですが、2019/12ではまだ公式に解決はしていないみたいです…

Core codeを編集しよう☆って言ってる人もいるようですが下記方法で実装できたため覚書です:writing_hand:

そもそも何故起きているのか

公式 Line 529
( https://github.com/kenwheeler/slick/blob/master/slick/slick.js#L529 )

if (_.options.centerMode === true || _.options.swipeToSlide === true) {
    _.options.slidesToScroll = 1;
}

centerMode か swipeToSlide が true のときはslidesToScrollの値が自動で決められてしまうようです。

なので、ここをコメントアウトすれば手っ取り早く解決しますが、他の箇所で影響出てくる可能性があります:no_good:

slickSetOption を使う

指定したオプションを変更するメソッドで、Ver1.4以降で使用可能とのこと。
1.png
methods.png

どのように使用するか

$('.slider').slick('slickSetOption', option, value, refresh);
  • 第1引数(オプション名)
  • 第2引数(オプションの値)
  • 第3引数(boolean / trueの場合動く) (第3引数はオプションなので書かなければtrueになります)

centerModeで3枚ずつ動かすとすれば、

$('.slider').slick("slickSetOption", "slidesToScroll", 3, true);

手順は
1. Slickを動かす
2. 自動でslidesToScrollが1になる
3. 後からslidesToScrollの値を変える
で良さそうです。

実装

$(document).ready(function(){
  const target = $('.slider_box')
  target.slick({
  slidesToShow: 3,
  slidesToScroll: 3,
  dots: false,
  centerMode: true,
  prevArrow: '<button class="prev arrow">←</button>',
  nextArrow: '<button class="next arrow">→</button>'
  });
  target.slick("slickSetOption", "slidesToScroll", 3);
});

See the Pen slider 3 by Sota Takahashi (@Sota) on CodePen.

このメソッドでイベントに合わせてスライダーの挙動を変えたりするのが簡単にできるのでは、と思います。

参考サイト

When centerMode is set to true the slidesToScroll property setting is ignored. #2328
https://github.com/kenwheeler/slick/issues/2328

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

Akashic Engine でライティング

Akashic Advent Calendar 2019 二十五日目の記事です。

Akashic Engine はフラグメントシェーダを利用できます。これを使ってゲーム画面に様々なポストエフェクトを適用することができます。この記事ではフラグメントシェーダを利用したライティングを実装してみます。

ゴールの確認

最初に完成形を確認しましょう。

prettycatinthelight.gif

スポットライトに照らされて、可愛らしい猫の描かれたポスターが浮かび上がっています。

ライティングの考え方

ライトには点光源(一点から全方向を照らす光源)や平行光源(無限遠にある光源)があります。この記事ではゲームで使いでがありそうなスポットライトを実装します。

ゲーム画面の一部を照らすことを考える時、二次元上の処理で済ませることもできそうですが、ここでは次の図のようにモデル化してみます。

fig01.png

UV平面はフラグメントシェーダが扱うテクスチャの平面です。これにW軸を追加します。高いところから懐中電灯で照らしている、というイメージです。

スポットライトはある方向を強く照らし、その方向から離れるほど減衰するものです。次の図の、光源から伸びる矢印がスポットライトの方向です。そこから離れるほど減衰し、破線の外側を照らしません。

fig02.png

スポットライトのパラメータは次のようになります。

  • 位置
  • 向き
  • カットオフ
    • スポットライトの範囲を決める角度。
  • 明るさ
  • 指数
    • スポットライトの方向から離れるにつれて光を弱くする値。
  • 環境光
    • スポットライトの範囲外の明るさ。

具体的な計算はコード上で確認してください。

完成

シェーダは次のようになります。

#version 100

precision mediump float;

uniform sampler2D uSampler;
varying vec2 vTexCoord;

// テクスチャの解像度。
uniform float image_width;
uniform float image_height;

// スポットライトのパラメータ。
// スポットライトの位置。
uniform float light_pos_x;
uniform float light_pos_y;
uniform float light_pos_z;
// スポットライトの方向。
uniform float light_dir_x;
uniform float light_dir_y;
uniform float light_dir_z;
// スポットライトの明るさ。
uniform float light_intensity;
// スポットライトの範囲。
uniform float light_cutoff;
// スポットライトの指数(大きくなるほど速く減衰する)。
uniform float light_exp;
// 環境光。スポットライトの範囲外の明るさ。
uniform float light_ambient;

// スポットライトの計算
//
// uv: スポットライトで照らされる画像上の位置。
// n: 画像の法線。
// lightPos: スポットライトの位置。
// cutoff: スポットライトの範囲。
// itensity: スポットライトの明るさ。
float spotLighting(vec2 uv, vec3 lightPos, vec3 spotDir, float cutoff, float exponent, float intensity) {
    // 画像上の一点からみた光源の方向。
    vec3 lightDir = normalize(lightPos - vec3(uv, 0.));
    // 画像上の一点に届く光線がスポットライトの方向からどの程度離れているか、を表す量(離れ具合の角度の余弦)。
    float spotCos = dot(spotDir, -lightDir);
    // スポットライトに照らされる範囲にあるか。
    if (spotCos >= cutoff) {
        return max(dot(vec3(0., 0., 1.), lightDir), 0.) // UV平面に対して光線が斜めに当たるほど弱くなるようにする。
             * pow(spotCos, exponent) // スポットライトの向きから外れた光線ほど減衰するようにする。
              * intensity; // スポットライト本来の明るさ。
    } else {
        // スポットライトの範囲外は照らされない。
        return 0.;
    }
}

void main() {
    vec2 uv = vTexCoord;

    vec3 lightPosition = vec3(light_pos_x, light_pos_y, light_pos_z);
    vec3 spotDir = normalize(vec3(light_dir_x, light_dir_y, light_dir_z));
    float intensity = spotLighting(uv, lightPosition, spotDir, light_cutoff, light_exp, light_intensity);

    vec3 ambient = vec3(light_ambient);
    vec3 lightColor = vec3(1., 1., 1.);
    vec4 color = texture2D(uSampler, uv);

    gl_FragColor = vec4(color.rgb * ambient + color.rgb * lightColor * intensity, 1.);
}

スポットライトの実装は以上です。デモはGitHubから入手できます。

応用: 立体感を出す

spotLighting() の計算の中に vec3(0., 0., 1.) という定数があります。これはUV平面がW軸正の方向を向いている、という値です。

この値を変化させると、まるで画像の表面に凹凸があるかのようなライティングが可能になります。次の例では凹凸を画像の輝度から求めています。レンガの壁に立体感がでました。

prettycatemboss.gif

最後に

この記事ではフラグメントシェーダでスポットライトを実装しました。ホラー系ノベルゲームや、アドベンチャーゲームの推理パートで使えそうですね。

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

N予備校のプログラミング入門コースの難易度がおかしい

私 is 誰

今年の7月にドワンゴの教育事業部に異動し、N予備校でプログラミング講師をやることになりました。

現在は週2回ニコ生やN予備校上にてプログラミング入門コースの授業放送をしています。
授業中はniconicoのTOPページに放送が出ており途中まで無料で視聴できるので、興味があれば是非ご視聴ください。直前の授業放送はこちら(ニコ生)

ドワンゴ自体は7年目となり、ニコニコ動画の開発を4年、エンジニア教育やエンジニア採用を2年ほどやってきました。

この記事で書きたいこと

現部署に異動後、教材のインプットを兼ねて『N予備校プログラミング入門コース』を履修したのですが、明らかに難易度が僕の想像した "入門コース" から外れたガチ編成になっていて衝撃を受けたことが記事を書こうと思ったきっかけです。
中身としてはとても良い教材になっているので、僕のような勿体無い誤解が少しでも減れば幸いです。

入門コースはいわゆる入門コースではない

『プログラミング入門コース』のゴールは ドワンゴがエンジニアとして採用したいレベルIT企業のエンジニアインターンに行けるレベル を目指しています。一般的な『入門』でイメージする難易度ではなく、かなり難易度の高い実践的な内容となっています。

ざっくり説明すると、入門コース最後の節では、

  • VirtualBox上に構築したUbuntu環境から
  • Node.js,Express,Webpack,PostgreSQLにて制作したGitHub認証入りのWebサービスを
  • テストコードやCI、脆弱性対策も完備して
  • Herokuにgit pushしてデプロイする

までを行います。入門とは一体……。

N予備校は高校生向けだけのサービスではない

予備校という名前から高校生向けの教材と思われがちですが、プログラミングコースやWebデザインコースは Webエンジニア、Webデザイナの就職をゴールとしたコース となっています。実際にプログラミングコースを受講している50%以上が社会人です。

大学受験コース

大学受験を目指す高校生向けの一般教科(数英国理社)が学べるコースです。
こちらは高校生向けのコースとなります。

プログラミングコース/Webデザインコース

現役プログラマやデザイナが教材制作および監修をしている、就職やキャリア開発をゴールに置いた実践的なコースとなります。
プログラミングコースの応用コースでは、Scalaでの大規模Web開発やスマホアプリ(iOS、Android)開発なども学ぶことができます。

この記事の対象者

現役Webエンジニアの方

本サービス、教材のインフルエンサーとしてもっとも適当な方々だと思います。
僕自身も4年間ニコニコ動画のWebエンジニアをやっていましたが、その視点でもだいぶ実践的な内容となっているので、共感いただける方がいれば幸いです。

Web系企業のエンジニア採用担当者

2年間エンジニア採用をやってきた身としても、プログラミング入門コース履修者はオススメできる人材です。Linux上での開発、gitの操作、セキュリティ知識など、個人では身につけにくい知識を得られるので、戦力になるまでが早いと思います。

Webエンジニア研修担当者

ドワンゴでもN予備校プログラミングコースの一部をエンジニア向けの研修に利用していました。Webエンジニアを育てるための統一的な研修教材になるかと思いますので、一考いただければ幸いです。

『プログラミング入門 Webアプリ』コースでやること

第1章 - はじめよう -

第1章では基礎的なHTML,CSS,JSの書き方を学び、章の終わりにはそれらを繋ぎ合わせて「入力内容で結果の変わる診断アプリ」(診断メーカーのようなもの)を作ります。

第1章は割とどこのサービスでも共通のプログラミング入門的なコンテンツで、初心者が脱落しないようやさしく解説されています。
対話的なプログラミングや手早く動くものが作れることを重点に置いているので、プログラミングの面白さを体感しながら読み進められるかと思います。

VSCodeで開発をする

まず最初にVSCodeをインストールし、開発も基本的にVSCode上で行っていきます。
VSCodeで開発するための環境設定なども併せて案内しているので、IDEのカスタマイズに関する関心もつけられます。

HTML、CSS、JS

基本的なタグやスタイルの当て方、HTMLに直接書く方法から外部ファイルに分割して読み込む方法まで学びます。Tweetボタンや動画の埋め込みなども学びます。手軽にリッチ化できる点が良い体験になるかと思います。

JSはES6(ES2015)を採用しており、以下の基本的な構造化プログラミングの方法について学習します。

  • 型(数値、文字列、真偽値)
  • 変数
  • 算術演算、比較演算、論理演算
  • if, for
  • コレクション
  • コメント
  • 関数
  • オブジェクト

ChromeのコンソールタブをJavaScriptのインタプリタとして使っており、1行1行の挙動を確認しながら学んでいくスタイルになります。
浮動小数や丸め誤差、正規表現、truthy/falsyな値、無名関数やアロー関数など、細かい部分の言語仕様もTipsとして解説されています。

診断ページを作る

基本的な言語仕様が学べたら診断ページを作ります。
要件定義からモックアップを作り、実装後は簡単なテストも書きます。

作って終わりではなく、開発の基本的なプロセスを体験できる進行となっています。

第2章 - 準備しよう -

第1章とは打って変わっていきなりガチな開発環境を整えていきます。
正直に言って技術的な関心がないと全く楽しくない、難所となる章です。

長くなるので学ぶ要素の列挙だけになりますが、Web開発経験のある人ならカリキュラムのヤバさ解ってもらえるかと思います。

オススメPoint

  • 殆どの学習をUbuntu上で行うため、WinとMacの環境差がなくなる
  • 能動的に学びにくいLinuxの操作や泥臭い処理を体系的に学べる
  • gitでのソーシャルコーディング(チーム開発に必要な操作)を学べる
  • ニコ動のランキングRSSを取ってきて集計する演習が楽しい

第2章で学べるもの

  • VirtualBox, Vagrantを使いUbuntu環境を作る
  • コンソール、ターミナルの使い方
  • linux
    • ライセンスについて
    • 基本的なLinux上でのコマンド操作(CUI、オプション、引数など)
    • SSH接続、SSHkeyGen、公開鍵認証
    • コンピュータを構成するデバイスについて
    • lshw を使った仮想デバイスのスペック確認
    • 手元端末との共有フォルダ設定(Vagrantfileによるマウント)
    • 標準入出力、エラー出力、リダイレクトやパイプ
    • シェルプログラミング、ファイル権限
    • tmuxによる複数窓の立ち上げ
  • 通信の話
    • tcpdump, nc, telnetを使って通信を確認
    • 各種通信プロトコルやTCPとUDP
    • HTTP通信の基礎
    • DNS
    • hosts設定、ポートフォワード設定
  • 実践系
    • niconico動画ランキング情報のRSSを取得してファイルに保存
    • cronによる定期実行
  • git
    • バージョン管理とは
    • 分散型バージョン管理とgitの基本的な使い方
    • コンフリクトの解消の実践
    • Githubでのイシュー管理、ドキュメンテーション、Gist
    • ブランチ戦略、フォーク、プルリクエスト

第3章 - サーバーサイドプログラミング入門 -

辛い2章を終えたところで、ようやくWeb開発っぽい部分に着手していきます。
プログラミング的な要素も増えてくるので割と楽しめる内容かと思います。

オススメPoint

  • SlackBotが作れるようになる
  • 管理者機能の必要性や実装に触れている
  • Herokuでお手軽にサーバーを立てる方法を学べる
  • 脆弱性に関する解説と攻撃の再現、対策の実装について学習できる

第3章で学べるもの

  • Node.js
    • ライブラリ、パッケージマネージャー
    • パッケージの作成
    • REPL
    • fs, Stream
    • 同期IO,非同期IO
  • JavaScript応用
    • 連想配列、for-of
    • map functions
  • アルゴリズムやパフォーマンス
    • フィボナッチ数列を求めるプログラムを書く
    • 再帰、メモ化、オーダー
    • timeコマンドやnode --profによる実行時間測定
  • SlackのBot開発
    • Slackワークスペースの作成
    • yo, Hubot, CoffeeScript
    • CRUD
    • 例外処理
  • Webアプリ作成
    • UI, URI, モジュール設計
    • Node.jsによるcreateServer
    • User-Agent
    • アクセスログ、ロギング
    • HTMLのフォーム
    • テンプレートエンジン
    • Basic認証によるログイン、ログアウト
    • Cookieとセッション管理
    • リダイレクト
    • ルーティング
    • データベース(PostgreSQL)
    • スタイル当て(Bootstrap)
    • 管理系機能の実装
    • HerokuでのWebサービス公開
  • セキュリティ、脆弱性(実際に再現と対策まで行う)
    • XSS
    • パスワードアタック
    • セッションハイジャック
    • CSRF

第4章

第4章は実践編になります。
各種フレームワークの導入や、実践的な機能の実装をしていきます。

終了時の成果物として、『予定調整サービス』がWeb上に公開された状態でできあがります。
これまで学んできたものを総動員して、『実用的なアプリケーションを作っていくプロセス』を学びます。

オススメPoint

  • 古すぎない枯れた(成熟した)フレームワークを学べる
  • 今までの学習を裏付けにしながら実用的なWebアプリケーションを作れる
  • ある程度作り込まれたサービスを題材にCIやリファクタリングの有用性を学べる

第4章で学べるもの

  • 各種フレームワークの導入
    • Webフレームワーク(Express)
    • passportによるGitHubログインの実装
    • テスティングフレームワーク(mocha)
    • webpack
  • 実践的なDOM操作(イベントハンドリングやアニメーション)
  • AJAX/WebSocket(Socket.IO)
  • CI(継続的インテグレーション)
  • リファクタリング
  • データベース操作
    • データモデリング
    • 基本的なデータ操作
    • JOIN
    • INDEXING
    • グルーピングと集計関数
    • トランザクション
  • 予定調整サービスの制作
    • 実践的なサービス設計、データ設計、要件定義
    • 外部認証の実装とユーザーの保存
    • 「予定」の作成と一覧表示
    • 「予定」の更新、削除、ユーザーごとの権限判定
    • Header、Footerの共通化
    • スタイル当て、セキュリティ対策、Herokuでの公開

おわりに

N予備校は 月額1000円 で上記教材が全て利用でき、Webデザインコースや大学受験コースも追加課金なしで受講可能です。

中の人が言うのもアレですが鬼安いので超オススメです。よろしくお願いします。

ちなみにN高等学校(通学コース/ネットコース)に入学すると、N予備校は無料で使うことができます。

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

N予備校プログラミング入門コースの難易度がおかしい

私 is 誰

今年の7月にドワンゴの教育事業部に異動し、N予備校でプログラミング講師をやることになりました。

現在は週2回ニコ生やN予備校上にてプログラミング入門コースの授業放送をしています。
授業中はniconicoのTOPページに放送が出ており途中まで無料で視聴できるので、興味があれば是非ご視聴ください。直前の授業放送はこちら(ニコ生)

明日(12/24)の授業ではNode.js(Express)でpassportを使ったGitHubログイン(OAuth2.0)を実装します。

ドワンゴ自体は7年目となり、ニコニコ動画の開発を4年、エンジニア教育やエンジニア採用を2年ほどやってきました。

この記事で書きたいこと

現部署に異動後、教材のインプットを兼ねて『N予備校プログラミング入門コース』を履修したのですが、明らかに難易度が僕の想像した "入門コース" から外れたガチ編成になっていて衝撃を受けたことが記事を書こうと思ったきっかけです。
中身としてはとても良い教材になっているので、僕のような勿体無い誤解が少しでも減れば幸いです。

入門コースはいわゆる入門コースではない

『プログラミング入門コース』のゴールは ドワンゴがエンジニアとして採用したいレベルIT企業のエンジニアインターンに行けるレベル を目指しています。一般的な『入門』でイメージする難易度ではなく、かなり難易度の高い実践的な内容となっています。

ざっくり説明すると、入門コース最後の節では、

  • VirtualBox上に構築したUbuntu環境から
  • Node.js,Express,Webpack,PostgreSQLにて制作したGitHub認証入りのWebサービスを
  • テストコードやCI、脆弱性対策も完備して
  • Herokuにgit pushしてデプロイする

までを行います。入門とは一体……。

N予備校は高校生向けだけのサービスではない

予備校という名前から高校生向けの教材と思われがちですが、プログラミングコースやWebデザインコースは Webエンジニア、Webデザイナの就職をゴールとしたコース となっています。実際にプログラミングコースを受講している50%以上が社会人です。

大学受験コース

大学受験を目指す高校生向けの一般教科(数英国理社)が学べるコースです。
こちらは高校生向けのコースとなります。

プログラミングコース/Webデザインコース

現役プログラマやデザイナが教材制作および監修をしている、就職やキャリア開発をゴールに置いた実践的なコースとなります。
プログラミングコースの応用コースでは、Scalaでの大規模Web開発やスマホアプリ(iOS、Android)開発なども学ぶことができます。

この記事の対象者

現役Webエンジニアの方

本サービス、教材のインフルエンサーとしてもっとも適当な方々だと思います。
僕自身も4年間ニコニコ動画のWebエンジニアをやっていましたが、その視点でもだいぶ実践的な内容となっているので、共感いただける方がいれば幸いです。

Web系企業のエンジニア採用担当者

2年間エンジニア採用をやってきた身としても、プログラミング入門コース履修者はオススメできる人材です。Linux上での開発、gitの操作、セキュリティ知識など、個人では身につけにくい知識を得られるので、戦力になるまでが早いと思います。

Webエンジニア研修担当者

ドワンゴでもN予備校プログラミングコースの一部をエンジニア向けの研修に利用していました。Webエンジニアを育てるための統一的な研修教材になるかと思いますので、一考いただければ幸いです。

『プログラミング入門 Webアプリ』コースでやること

第1章 - はじめよう -

第1章では基礎的なHTML,CSS,JSの書き方を学び、章の終わりにはそれらを繋ぎ合わせて「入力内容で結果の変わる診断アプリ」(診断メーカーのようなもの)を作ります。

第1章は割とどこのサービスでも共通のプログラミング入門的なコンテンツで、初心者が脱落しないようやさしく解説されています。
対話的なプログラミングや手早く動くものが作れることを重点に置いているので、プログラミングの面白さを体感しながら読み進められるかと思います。

VSCodeで開発をする

まず最初にVSCodeをインストールし、開発も基本的にVSCode上で行っていきます。
VSCodeで開発するための環境設定なども併せて案内しているので、IDEのカスタマイズに関する関心もつけられます。

HTML、CSS、JS

基本的なタグやスタイルの当て方、HTMLに直接書く方法から外部ファイルに分割して読み込む方法まで学びます。Tweetボタンや動画の埋め込みなども学びます。手軽にリッチ化できる点が良い体験になるかと思います。

JSはES6(ES2015)を採用しており、以下の基本的な構造化プログラミングの方法について学習します。

  • 型(数値、文字列、真偽値)
  • 変数
  • 算術演算、比較演算、論理演算
  • if, for
  • コレクション
  • コメント
  • 関数
  • オブジェクト

ChromeのコンソールタブをJavaScriptのインタプリタとして使っており、1行1行の挙動を確認しながら学んでいくスタイルになります。
浮動小数や丸め誤差、正規表現、truthy/falsyな値、無名関数やアロー関数など、細かい部分の言語仕様もTipsとして解説されています。

診断ページを作る

基本的な言語仕様が学べたら診断ページを作ります。
要件定義からモックアップを作り、実装後は簡単なテストも書きます。

作って終わりではなく、開発の基本的なプロセスを体験できる進行となっています。

第2章 - 準備しよう -

第1章とは打って変わっていきなりガチな開発環境を整えていきます。
正直に言って技術的な関心がないと全く楽しくない、難所となる章です。

長くなるので学ぶ要素の列挙だけになりますが、Web開発経験のある人ならカリキュラムのヤバさ解ってもらえるかと思います。

オススメPoint

  • 殆どの学習をUbuntu上で行うため、WinとMacの環境差がなくなる
  • 能動的に学びにくいLinuxの操作や泥臭い処理を体系的に学べる
  • gitでのソーシャルコーディング(チーム開発に必要な操作)を学べる
  • ニコ動のランキングRSSを取ってきて集計する演習が楽しい

第2章で学べるもの

  • VirtualBox, Vagrantを使いUbuntu環境を作る
  • コンソール、ターミナルの使い方
  • linux
    • ライセンスについて
    • 基本的なLinux上でのコマンド操作(CUI、オプション、引数など)
    • SSH接続、SSHkeyGen、公開鍵認証
    • コンピュータを構成するデバイスについて
    • lshw を使った仮想デバイスのスペック確認
    • 手元端末との共有フォルダ設定(Vagrantfileによるマウント)
    • 標準入出力、エラー出力、リダイレクトやパイプ
    • シェルプログラミング、ファイル権限
    • tmuxによる複数窓の立ち上げ
  • 通信の話
    • tcpdump, nc, telnetを使って通信を確認
    • 各種通信プロトコルやTCPとUDP
    • HTTP通信の基礎
    • DNS
    • hosts設定、ポートフォワード設定
  • 実践系
    • niconico動画ランキング情報のRSSを取得してファイルに保存
    • cronによる定期実行
  • git
    • バージョン管理とは
    • 分散型バージョン管理とgitの基本的な使い方
    • コンフリクトの解消の実践
    • Githubでのイシュー管理、ドキュメンテーション、Gist
    • ブランチ戦略、フォーク、プルリクエスト

第3章 - サーバーサイドプログラミング入門 -

辛い2章を終えたところで、ようやくWeb開発っぽい部分に着手していきます。
プログラミング的な要素も増えてくるので割と楽しめる内容かと思います。

オススメPoint

  • SlackBotが作れるようになる
  • 管理者機能の必要性や実装に触れている
  • Herokuでお手軽にサーバーを立てる方法を学べる
  • 脆弱性に関する解説と攻撃の再現、対策の実装について学習できる

第3章で学べるもの

  • Node.js
    • ライブラリ、パッケージマネージャー
    • パッケージの作成
    • REPL
    • fs, Stream
    • 同期IO,非同期IO
  • JavaScript応用
    • 連想配列、for-of
    • map functions
  • アルゴリズムやパフォーマンス
    • フィボナッチ数列を求めるプログラムを書く
    • 再帰、メモ化、オーダー
    • timeコマンドやnode --profによる実行時間測定
  • SlackのBot開発
    • Slackワークスペースの作成
    • yo, Hubot, CoffeeScript
    • CRUD
    • 例外処理
  • Webアプリ作成
    • UI, URI, モジュール設計
    • Node.jsによるcreateServer
    • User-Agent
    • アクセスログ、ロギング
    • HTMLのフォーム
    • テンプレートエンジン
    • Basic認証によるログイン、ログアウト
    • Cookieとセッション管理
    • リダイレクト
    • ルーティング
    • データベース(PostgreSQL)
    • スタイル当て(Bootstrap)
    • 管理系機能の実装
    • HerokuでのWebサービス公開
  • セキュリティ、脆弱性(実際に再現と対策まで行う)
    • XSS
    • パスワードアタック
    • セッションハイジャック
    • CSRF

第4章

第4章は実践編になります。
各種フレームワークの導入や、実践的な機能の実装をしていきます。

終了時の成果物として、『予定調整サービス』がWeb上に公開された状態でできあがります。
これまで学んできたものを総動員して、『実用的なアプリケーションを作っていくプロセス』を学びます。

オススメPoint

  • 古すぎない枯れた(成熟した)フレームワークを学べる
  • 今までの学習を裏付けにしながら実用的なWebアプリケーションを作れる
  • ある程度作り込まれたサービスを題材にCIやリファクタリングの有用性を学べる

第4章で学べるもの

  • 各種フレームワークの導入
    • Webフレームワーク(Express)
    • passportによるGitHubログインの実装
    • テスティングフレームワーク(mocha)
    • webpack
  • 実践的なDOM操作(イベントハンドリングやアニメーション)
  • AJAX/WebSocket(Socket.IO)
  • CI(継続的インテグレーション)
  • リファクタリング
  • データベース操作
    • データモデリング
    • 基本的なデータ操作
    • JOIN
    • INDEXING
    • グルーピングと集計関数
    • トランザクション
  • 予定調整サービスの制作
    • 実践的なサービス設計、データ設計、要件定義
    • 外部認証の実装とユーザーの保存
    • 「予定」の作成と一覧表示
    • 「予定」の更新、削除、ユーザーごとの権限判定
    • Header、Footerの共通化
    • スタイル当て、セキュリティ対策、Herokuでの公開

おわりに

N予備校は 月額1000円 で上記教材が全て利用でき、Webデザインコースや大学受験コースも追加課金なしで受講可能です。

中の人が言うのもアレですが鬼安いので超オススメです。よろしくお願いします。

ちなみにN高等学校(通学コース/ネットコース)に入学すると、N予備校は無料で使うことができます。

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

Chrome拡張機能開発の公式チュートリアルを解説+補足 前編

はじめに

GoogleのChrome拡張の公式が出してるチュートリアルを解説します。
僕なりに分かりやすくなるように、公式の流れに沿って書こうと思います。
ただの翻訳といえば否定できませんが、それだけだとつまらないので、ところどころ補足欲しいなぁ〜て自分が感じたところを付け足していきます。
僕は英語が嫌いなので、僕と同じように英語で書いてあるサイト読むのだりぃ〜って人の役に立てばと思います。

説明に入る前に、僕が以前書いたこの記事

を読んでいただけると少しは理解しやすくなるかと思います。
分かりにくかったら、その記事に載せてる参考文献が分かりやすいので、そちらを見てください。

要約すると、Chrome拡張でやりたいことを実現するには

  • Background
  • Contents Script
  • Popup

の3つの世界を使い分ける必要があるってことです。
理解していただけたら、次に進みましょう。

チュートリアル

さっそく補足ですが、まずは色々なファイルを入れる用のフォルダを作っておきましょう。
場所はどこでも良いです。名前も何でも良いです。
僕は試しに、デスクトップに「testChromeExtension」というフォルダを作りました。
フォルダ作成
ここに、これから作成するファイルや、アイコンの画像などを入れていきます。
それでは作成していきます。

1. マニフェストファイルの作成

「Manifest = 公約」という意味から分かるように、決まりごとを書いていくファイルです。
これがないと拡張機能が作れません。
例えば、

  • この拡張機能の名前は何なのか
  • マニフェストのバージョンは何を使うのか
  • アイコンの画像はどれか
  • PopupのHTMLファイルはどれを使うのか

などをJSONファイルに書いていきます。
ファイル名は「manifest.json」にしましょう。
公式に書いてあるのはこれです↓

manifest.json
{
    "name": "Getting Started Example",
    "version": "1.0",
    "description": "Build an Extension!",
    "manifest_version": 2
}

とりあえずこれだけです。
簡単に補足すると、

  • name → 拡張機能の名前(自分で決める)
  • version → 拡張機能のバージョン(自分で決める)
  • description → 拡張機能の説明(自分で決める)
  • manifest_version → マニフェストのバージョン(これは固定、現在*投稿日現在*は2を書く)

という意味になります。
自分用に書き換えると、こうなります↓

manifest.json
{
    "name": "Test Chrome-Extension",
    "version": "1.0",
    "description": "これはChrome拡張機能のテストです。",
    "manifest_version": 2
}

2. アップロード

マニフェストファイルを作り終えたら、最初に作成したフォルダに保存しましょう。
マニフェストファイルを保存

早速これをアップロードしていきます。
ローカルで実行するだけですので、実際に拡張機能のストアに公開するわけではありません。
chrome://extensions/ を開きましょう。
アップロード手順

(0. まず、⓪のデベロッパーツールをオンにして、「パッケージ化されていない拡張機能を読み込む」、「拡張機能をパッケージ化」、「更新」の3つのボタンを表示させましょう。)
 1. ①の「パッケージ化されていない拡張機能を読み込む」というボタンを押しましょう。
  英語だと、「LOAD UNPAKED」と書いてあるかと思います。
 2. フォルダを選べるようになるので、最初に作成してマニフェストファイルを入れたフォルダ(②)を選択します。
 3. 選択!(③)

アップロード完了
すると、マニフェストファイルに書き込んだ名前・バージョン・説明・自動生成されたIDが表示されてるかと思います。
アイコンはまだ設定していないので、デフォルトのままですね。
表示されていたら成功です。
表示されていなかったら、ファイル名「manifest.json」やJSONに記入ミスが無いか、manifett_version はちゃんと 2 になっているかを確認してください。

以上がアップロードの手順です。
これからファイルを更新したら、右下のラジオボタンの隣にある更新マークを押せば、更新できます。
反映されない場合は、一旦削除してから再びアップロードするなどを試してみてください。

3. Backgroundファイルの作成

まずは、マニフェストファイルに、「Backgroundのコードはこのファイルに書くよ!」ということ教えてあげる必要があるます。
公式と同じように追加します。

manifest.json
{
    "name": "Test Chrome-Extension",
    "version": "1.0",
    "description": "これはChrome拡張機能のテストです。",

    "permissions": ["storage"],
    "background": {
      "scripts": ["background.js"],
      "persistent": false
    },

    "manifest_version": 2
}

BackgroundファイルはJavaScriptで記述します。
ファイル名は何でも良いですが、無難に「background.js」とでもしておきましょう。

それぞれの説明をします。

  • permission → 使うAPI等を記述します。今回はstrage APIを使用します(詳細は後述)。
  • background → Backgroundに関しての色々を記述します。
    • script → Backgroundファイルのファイル名を記述します。
    • persistent → 永続的に動くか否か。falseにすることで、backgroundファイルが呼ばれた(通信する時)のみ動きます。永続的に動いているとメモリが無駄に消費されてしまうので、falseにすることをお勧めします。

では、実際にBackgroundファイルを作成していきます。

background.js
chrome.runtime.onInstalled.addListener(function() {
    chrome.storage.sync.set({color: '#3aa757'}, function() {
        console.log("The color is green.");
    });
});

ここで使用するstorage APIは、名前の通りデータを保存してくれるストレージのAPIです。
今後書くContents Scriptファイルに書くことで、わざわざBackgroundにデータを移さなくても、データを保持してくれるみたいです。
ただ、容量がそんなに大きく無いのと、セキュリティがガバガバなので個人情報などは扱わない方が良い、というのが注意点です。

  • chrome.runtime.onInstalled.addListener(function() {}); → 拡張機能がインストールされたときや更新された時に関数内を実行する。
    • chrome.storage.sync.set({color: '#3aa757'}, function() {}); → 先ほど許可したstorage APIの命令です。colorというKeyに#3aa757というValueをセットしています。
      • console.log("The color is green."); → コンソールに文字を出力します。デバッグなどに使えます。

とりあえずこれらをコピペして、フォルダに保存してください。
スクリーンショット 2019-12-23 16.28.19.png
ファイルが保存できたら、chrome://extensions/ で拡張機能を更新しましょう。

すると、「バックグラウンド ページ」というリンクができているかと思います。
拡張機能更新
そのリンクを押すと、Backgroundのデベロッパーツールが表示されます。
background.jsに書いたコンソール出力は、ここで確認することができます。
Background_コンソール
これでBackgroundでChrome拡張を開発することはできました。
ただ、まだ見た目には何も変化がないですが、重要な準備段階です。
後日、残りのチュートリアルの解説をしていきます

おわりに

今回はここまでです。
あと中編と後編を書く予定です。
書けたらリンクを貼ります。

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

lodash やめ方

みなさん、 lodash で消耗してますか? 私は消耗しています。

なぜ lodash で消耗するかというと、とにかく思考停止でインストールされ、 node_modules 下で大量に重複します。サイズが大きいlodashが複数バンドルされてビルドされると、重篤なパフォーマンス上の問題を引き起こします。

https://bundlephobia.com/result?p=lodash@4.17.15

lodash には実装上の問題もあり、異様に丁寧に、そして富豪的に作られており、その結果ビルドサイズが無駄に大きいです。丁寧に作られて入るのですが、現代のフロントエンド水準や一般的なポリフィルと噛み合っていません。というわけで、常々やめたいと思っています。

ちゃんとES201xを追ってる人からすると、ほとんどの lodash のメソッドは不要に見えるはずです。本エントリは、思考停止で lodash で実装しようとする人に、ちょっと考え直しては? と投げつける用の記事になります。

現代においては、次の選択肢があります。

  • ES20xxの標準メソッドを使う
  • 簡易なメソッドで代替する
  • ES Modules で選択的に読み込む

ES20xxの標準メソッドを使う

まず言語組み込みのメソッドを検討してください。ES5 の filter や reduce や map は勿論、見落とされがちな以下のメソッドを確認してください。

  • Array.prototype.find
  • Array.prototype.findIndex
  • Array.prototype.flat
  • Array.prototype.flatMap
  • Array.prototype.includes
  • String.prototype.includes
  • String.prototype.padStart
  • Object.values
  • Object.assign
  • Object.entries
  • Object.fromEntries

本当に自分のサポートブラウザ範囲で使えるか不安な場合は、 caniuse で確認してみてください。

https://caniuse.com/#search=find

IEのような レガシーブラウザのサポートでは、 webpack 環境の場合 core-js の読み込み、 そうでない場合も埋め込みタグで UAから自動的に polyfill を出し分けしてくれる polyfill.io を検討してみてください。

(実際には core-js もバンドルサイズが大きい問題があるのですが、インスタンスの重複が置きづらいのと、core-js 自体の機能として選択的なローディングも可能なので、本記事ではスコープ外とします。)

簡易な代替メソッドを定義する

ほとんどのユースケースでは、 lodash の便利な機能は必要なく、素朴なメソッドで置換可能なはずです。

// _.range
// n 個の配列を展開する range 関数 ※ ちゃんとした range ではないです
const range = n => [...new Array(n).keys()];
range(3); // [0, 1, 2]

// _.uniqueId
let cnt = 0;
const uniqueId = () => cnt++;

簡易な置き換えが可能でないか、こちらを確認してみてください

https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore

ES Modules で選択的に読み込む

// npm install lodash-es --save
// webpack+uglify(terser) で tree shake されて消されます
import { sortBy } from "lodash-es";

// ファイル直接指定
import sortBy from "lodash-es/sortBy";


// npm install lodash.sortby --save
// 単体モジュールでの読み込み
import sortBy from "lodash.sortby";

この時 chain は絶対に使ってはいけま せん。これはメソッドチェーンで書けるようにするものですが、内部的にすべてのファイルを読み込みます。

とはいえ、これらも内部的に 他の lodash を芋づる式に import するので、実際にはそれなりのサイズになりがちです。やめるための中間状態として、こういう形式に書き換える、と考えておくといいです。

実際に読み込んでるものが高いもの

自分がコードを書く範囲では、以下のメソッドで lodash を選択的に使うことが多いです。逆に言うと、これ以外では使うことは、かなり稀です。

特殊なコレクション操作

  • sortBy
  • groupBy
  • difference

時系列系

  • throttle
  • debounce

(sortBy は JSに非破壊sortがないのが悪い)

lodash がビルドから消えたか確認する

webpack-bundle-analyzer でこれらが効果を出たか確認しましょう。

https://www.npmjs.com/package/webpack-bundle-analyzer

npm i -g webpack-bundle-analyzer
webpack --json | webpack-bundle-analyzer

自分のコードから削除して、その上で未だに lodash が消えてない場合、依存パッケージが読み込んでいる可能性が高いです。

HACK: ビルド時に yarn の resolutions で無理矢理一つにまとめる

どんなに自分が頑張ろうが、 node_modules が結局大量の lodash を引き連れてくると思います。悲しいですね。
そんな時に npm では無理矢理ですが yarn では、複数の lodash のバージョン指定を一つにまとめたりできます。

{
  "resolutions": {
    "lodash": "4.*",
  }
}

lodash 3系と4系は、主に contains => includes の breaking change で壊れがちなのですが、それさえ気をつければ3系への移行は簡単です。

不安な場合、 TypeScript と @types/lodash の導入で型チェックしながら置き換えるといいかもしれません。

おわりに

実際には lodash が問題というより、 フロントエンド知識のアップデートをしていない という姿勢のシグナルとして、安易な lodash が現れることが多いです。こういう人を見つけたらこの記事を投げつけてください。

まず、lodash で、という姿勢を改めて、 lodash のメソッド一覧を読む前に https://developer.mozilla.org/ja/ を読むとか、そういう習慣を見に付けるといいと思います。

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

Vue CLIでconsole.logを有効化する方法

はじめに

Vue CLI(ESLint)のデフォルト設定では、console.logがエラーを吐く仕様になっています。
でも簡単なデバッグなどでは手軽に使えたら嬉しいですよね。
そこで、Vue CLIでconsole.logを有効にする方法をメモしておきます。
非常に簡単なので、お困りの方がいらしたらぜひお試しください。

環境

Vue CLIバージョン:3.9.3

1. 永続的に有効化したい場合

■変更対象ファイル(src/package.json)
eslintConfig内のrules"no-console": 0を設定

package.json
"eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "rules": {
      "no-console": 0
    },
    "parserOptions": {
      "parser": "babel-eslint"
    }
  }

2. 一時的に有効化したい場合

console.logを使いたいファイルに、以下のコメントを追加

/* eslint-disable no-console */

.jsファイルであればファイルの一番上に、
.vueファイルであればscriptタグ内の一番上に記述すれば有効化されます!

他にも色々と設定方法が存在しますが、この2つの方法だけでも特に開発作業に問題はないかと思います。
以上です!!

ぺーぺーなので、間違いなどあったら優しく教えていただけると泣いて喜びます( ;∀;)

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

ナンの実りもないクソアプリを作った

ナンを咲かせる木を作った

題して「枯れ木にナンを咲かせましょう」

See the Pen 枯れ木にナンを咲かせましょう by Thugumi Ishimaru (@thugumi-ishimaru) on CodePen.

「ナンを咲かせる」ボタンを押したら木にナンが生るクソアプリを作りました。
20個までナンを咲かせることができます。

遊び方

  • 1. 「ナンを咲かせる」ボタンを押す
  • 2. ナンが咲く

以上!!!!!!!!!!!!とっても簡単です!!!!!!!!!!!誰でも遊べる!!!!!!!!!!
そしてクソアプリ!!!!!もちろんなんの役にも立たない!!!!!!!

アプリもクソなのですが、ソースもクソなので何がクソなのかをこちらに解説していこうと思います。
このアプリを反面教師にしていただけたらと思います。

クソアプリのここがクソ

クソ部分その1:何の得もない

この木にナンができたからといって、「......で?」で終わると思うんです。
そう、本当にナンの役にも立ちません。

クソアプリなんだからいいですよね。。。クソなんだから。。。

クソ部分その2:scssが長すぎる

ソースをみてみましょう。

index.html
<div class="nan"></div>
style.scss
.nan {
  width: 400px;
  height: 300px;
  position: absolute;
  top: -100px;
  &_item {
    position: absolute;
    z-index: 1;
    &:nth-child(1) {
      left: 0;
      top: 0;
    }
    &:nth-child(2) {
      left: 100px;
      top: 0;
    }
    &:nth-child(3) {
      left: 200px;
      top: 0;
    }
    &:nth-child(4) {
      left: 300px;
      top: 0;
    }
    &:nth-child(5) {
      left: 0;
      top: 100px;
    }
    &:nth-child(6) {
      left: 100px;
      top: 100px;
    }
    &:nth-child(7) {
      left: 200px;
      top: 100px;
    }
(中略)
    &:nth-child(20) {
      left: 200px;
      top: 100px;
    }

scssファイル読みづら!!!!!!!!
20個以上増やしたくなった時にまたscssに増やさんといけんとか頭痛くない?!
しかもいちいちズラすの?!?!?!?!??!
間に.nan_itemじゃないもの入ってきたら死ぬよ?!?!?!??!

と、色々出てくるんです。
じゃあどうすればいいか、、、今回はscssのみで解決できる方法をお伝えしようかと思います。

解決方法手順その1: ルールを明確にする

間に.nan_itemじゃないもの入ってきたら死ぬよ?!?!?!??!

なぜ死ぬか、nth-child(n)を使っているからです。
と思うのですが、その前にルールを明確にしていないのですね。
ではルールを明確にしてあげましょう。

ということで「.nan以下には.nan_itemしか入れちゃ嫌!!!」とルール化しちゃいましょう。コードで語るのです。
<ul>タグの配下には<li>のみ記述が許可されているのでそのルールを利用します。

  • div.nanul.nanに変更
  • div.nan_itemli.nanに変更
index.html
<ul class="nan"></ul>
script.js
$('.nan').append('<li class="nan_item"><img src="https://illust-shokudo.com/assets/item/other_00001.png" alt=""</li>');

これで、.nan.nan_itemの間に部外者を入れられなくなりました。

解決方法手順その2: for文でnth-child(n)を生成

20個以上増やしたくなった時にまたscssに増やさんといけんとか頭痛くない?!

大変面倒ですよね。次にこれを解決しましょう。
scssではfor文が使えるので、そちらを使用します。

style.scss
.nan {
  width: 400px;
  height: 300px;
  position: absolute;
  top: -100px;
  &_item {
    position: absolute;
    z-index: 1;
    @for $i from 1 through 20 {
      &:nth-child(#{$i}) {
       top: 0;
       left: 0;
      }
    }
  }
}

これでnth-child地獄は脱出しました。
しかし、このままでは配置位置が全て同じになってしまう。。。じゃあどうしよう。。。

次の手順をみていきましょう。

解決方法手順その3: 絶対配置の配置位置をランダムにする

このままでは配置位置が全て同じになってしまう。。。

以下の記事にもあるようにscssではrandom()が使用できます。
これによって、ランダムの数値を当てることができるようになります。
今回はナンの位置を数値で指定していますよね。こちらを使ってみましょう。
https://qiita.com/tonkotsuboy_com/items/909b4073459ecaf7a435

style.scss
.nan {
  width: 400px;
  height: 300px;
  position: absolute;
  top: -100px;
  &_item {
    position: absolute;
    z-index: 1;
    @for $i from 1 through 20 {
      &:nth-child(#{$i}) {
        left: random(300) + px;
        top: random(300) + px;
      }
    }
  }
}

これで全てのナンをバラバラの位置に表示することができます。

このリファクタを行ったナンの木がこちら

See the Pen 枯れ木にナンを咲かせましょう(リファクタバージョン) by Thugumi Ishimaru (@thugumi-ishimaru) on CodePen.

この実装のメリット

  • scssのみで実装可能
  • scssのソースがスッキリして読みやすくなる

この実装のデメリット

  • 数値はrandom()なので毎回うまく配置されるとは限らない。前提が「この位置に絶対配置するのである」の要件にはそぐわない。

まとめ

クソアプリと共にクソースコードを紹介しました!
クソコードを修正するのに、以下を明確にしています。

  • 枯れ木にナンを咲かせるクソアプリを作った

  • 何がクソか

    • 要素が増えたときに死ぬ
    • 長い
    • 読みづらい
  • scssで以下を修正してクソから少し脱却する

    • ルールを明確にする
    • for文を使用する
    • random()を使用する

枯れ木にナンを咲かせましょう!!!!!!!

おまけ

.nanって命名にしてますが、JavaScriptではNaNという予約語が存在するので、
こんがらがらないように気をつけましょう。
れむさんありとうございます!
https://www.javadrive.jp/javascript/ini/index5.html

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

はじめてのOSSコントリビュート

はじめに

「「「OSS貢献」」」がなにかとても偉大ですげぇことだと思っていた頃が自分にはありました。

筆者は新卒2年目のフリーランスWebエンジニアですが、ちょくちょくとJavaScript(TypeScript)のライブラリに軽微な修正PRを送るようになったのは、まだほんの最近のことです。

この記事では、筆者が執筆時点(2019/12)で実際に送っているPRを参考に、OSS貢献未経験者向けに流れをざっくり書きます。

※なお筆者は主にWebの開発者なので、業界が違えば様々なやり方や慣習が異なることはあらかじめ断っておきます。

本記事で取り上げるPR例

本記事では、Firebaseに最近追加されたFirebase Extensionsという拡張機能において、公式の拡張機能の一つである Resize Images というプラグインにバグを見つけたので、修正しました。

実際のPull Request:
https://github.com/firebase/extensions/pull/134

Firebase Extensionsについては、こちらのQiita記事などを読むと雰囲気が分かるかと思います。

発見したバグについて

Resize Images はFirebase Storage にアップロードされた画像を自動で指定したサイズに変換してくれる頼もしい拡張機能ですが、SDKを使ってアップロードする時に、対象referece名に拡張子がない場合、ファイル名が消えてしまう現象を発見しました。(下図やPR参照)

Screenshot 2019-12-13 02.44.02.png

ちなみに、Qiitaにもこの現象を報告した記事がありました。

この現象に初めて遭遇した時、文字列の操作に失敗している類のあるあるバグだなと勘付いたのでソースコードを眺めたところ、すぐに原因がわかりました。

公式repository: https://github.com/firebase/extensions

バグを修正する

Issue, PRの確認

いくら修正が簡単とはいっても、バグを発見した時にまず初めにするべきことは、関連するissueやPRが既に存在しないかどうか確認することです。
特にプロジェクトによっては、リリースされている最新版やmasterブランチでは未対応でも、次期リリースやnextブランチでは既に対応済みだったりすることもよくあります。

あるいは、まだマージされていないopenなPRが上がっていたり、過去に同じバグに対して送られたPRがなんらかの事情でcloseされていることもあるでしょう。

ですので、github上での検索やgit blameをうまく使いながら、現在のコードがなぜ今その状態であるのかを知ることが、まず1番初めにしなければならない作業です。

本記事で扱っているケースの場合は、関連するissueはいくつか上がっていたもののそれらしい修正PRはまだ誰も着手していないようだったので、自分で着手することにしました。

リポジトリをforkする

これは当たり前の話ですが、自分がcollaboratorではないリポジトリにpushすることはできないので、まずは自分のアカウントにリポジトリをforkします。
forkが完了したら、手元にgit cloneしましょう。

バグの再現とテストの作成

まずは、バグが確実に起きるテストケースを書いて、そのテストが確実に通らないことを確認します。
(ただし、本記事のPRの場合は、元々のコードにユニットテストが一切書かれていない上に、そもそもFirebase Extensionsをローカル環境で走らせる方法がない?為に、この手順をスキップせざるを得ませんでした。代わりに、PRの概要に発生する条件を書いてスクリーンショットを添付しました。とはいえ、自分が追加した部分のユニットテストはもちろん書きます。

修正の作成

壊れたテストがあれば、あとはそれを動くするようにするだけです。

この際に注意する点として、なるべくレビュワーの負担を抑えられるようにする為に修正は可能な限り小範囲に留め、またそのプロジェクトで使われているフレームワークや書き方をなるべく踏襲するようにすると、より早くマージしてもらえることでしょう。

対象動作環境について

また、そのコードが動作を期待されている環境についても、確認が必要です。
Node.jsのプロジェクトであれば、大体以下のような場所を確認すると、おおまかな動作環境は確認できます。

  • .node-version (nodenv)
  • package.jsonenginesフィールド
  • DockerFiledocker-compose.yml
  • circleci/config.yml のようなCIの設定ファイル

Firebase Cloud Functionsなどの場合は、ランタイムで動作がサポートされているNodeのバージョンが公式docsに載っていたりするので、その辺りを確認するのも良いでしょう。
https://firebase.google.com/docs/functions/manage-functions#set_nodejs_version

コミットについて

gitのコミットについては少し注意が必要です。
たとえば、Electronの場合は、コミットメッセージの書き方についてガイドラインに書いてあります
CONTRIBUTING.mdREADME.md、wikiなどに何も書いていなければ、git logを見て大体雰囲気を合わせたコミットをすれば良いかと思います。

ビルド生成物について

通常はあまりないパターンですが、本記事の例の場合は、ビルド時の生成物をgit管理しているプロジェクトだったので、ビルド後にそれらもコミットしました。

PRを送る

修正が完了したら、自分のリポジトリにpushし、本家にプルリクエストを送ります。

プルリクエストのテンプレートがあるようなプロジェクトであれば、それに沿ってプルリクエストの内容を書きましょう。
この時、実際に発生したバグのissueのurlを貼ったり、適切なラベルをつけることは大切です。
自分の作成したissueはもちろん、他人の作成したissueや関連のあるissue, PRなどにも、自分の作成したPRのリンクを貼り付けていけば、忙しいメンテナーの目に止まりやすくなり、スムーズにマージしてもらえるかもしれません。

また、コミュニケーションはなるべくフレンドリーにすることを心がけましょう。
これは個人的な意見ですが、自分の書いたコードに不備があったとしても、粗雑な言葉遣いで「壊れてから直してやったよ!」みたいに言われていい気がする人はいないでしょう。
手っ取り早い話で言えば、絵文字とかを使うと文が明るくなりますし、英語の婉曲表現を使うと穏やかに伝えられます。

例1:
❌Your implementation is wrong.
(あなたの実装は間違っている)
⭕️Your implementation might not be working as expected.
(あなたの実装は想定した通りに振舞っていないかもしれません
⭕️It looks like something is wrong.
(なにかが間違っているように見えます

例2:
❌I made a PR. Merge it.
(PRを作りました。マージしてください。)
⭕️I've challenged myself to create a fix. Could you please have a moment to take a look at it?
(頑張って直してみました。ちょっと見てみてくださいませんか?

最後に

英語に苦手意識があったり、OSSを遠い存在に感じる人もいるかもしれません。
筆者も色々なライブラリを読むようになってから気づいたのですが、意外なことに、有名なライブラリでもそんなに綺麗ではない実装がされていたりテストが不十分だったりと、猫の手も借りたいような状況のことはよくあります。

英語に自信のない人でも、Grammarly のようなツールを使えば、ある程度の文法の修正はできますし、僕の愛用しているVS Codeのプラグインである Code Spell Checkerを使えば、英単語の綴り間違いを簡単に修正できるので便利です。

自分への戒めなんですが、みんな「〇〇のライブラリは壊れている」とか「〇〇は使いにくい」とか言ってしまいがちです。
でも、ハマりどころを回避する記事を書く時間があるなら、OSSなら自分でそれを直してしまった方がメチャクチャかっこいいと思います。
そしてそれは、

It's easier than you think = 思ってるより簡単 (※Electronより引用)

です。

さあ、これを読んだあなたもまずはgithubで good first issuehelp wanted のタグのついているissueを探してガンガン貢献していきましょう!

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

Fetch API について

Fetch APIについて

この記事では、JavaScriptのFetch APIの簡単な使い方を紹介します。モチベーションとしては、「XMLHttpRequestだと記述が長い!」「コールバックが面倒!」です。

hac2019_2本目.png

なお、この記事ではPromiseとawaitは出てきますが、asyncは出てきません。

Fetch APIとは

Fetch APIとは、XMLHttpRequestと同じでHTTPリクエストを発行する APIですが、XMLHttpRequestよりシンプルでモダンな APIです。
Fetch APIのfetchを使えば、下記のような簡単な呼び出しで HTTPリクエストを発行して結果を見ることができます。実際、F12ツール(DevTools)上でリクエストを送ってみると、

(await fetch("https://qiita.com/api/v2/items")).json();

こんな結果が返ってきます。

(20) [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}]
  lastIndex: (...)
  lastItem: (...)
  0: {rendered_body: "<h2>...</h2>", body:"...", ...}
  1: {rendered_body: "<h2>...</h2>", body:"...", ...}
  <<中略>>
  19: {rendered_body: "<h2>...</h2>", body:"...", ...}
  length: 20
  __proto__: Array(0)

XMLHttpRequestを使うよりはシンプルですし、jQuery.ajaxやaxios.getのようにライブラリに依存しません。
その代わり、実行可能なブラウザには制限ができてしまいます。私は、APIからデータを取得してJavaScriptでちょっとした加工をしたいときに使っています。

基本的には、これだけ知っていれば十分です。他に知る必要があるのはヘッダやメソッドの指定の仕方くらいでしょうか。

使ってみる

なにはともあれ、使ってみましょう。
このページを見ながら、F12キーか Ctrl+Shift+Iを押して F12ツール(DevTools)を開いて、コンソールを開いてください。

リクエストを発行する

fetchを使ってリクエストを送ってみます。

fetch("https://qiita.com/api/v2/items");

するとこんな値が返ってきます。
Promiseです。中身がぜんぜん見えませんね。

Promise {<pending>}
  __proto__: Promise
  [[PromiseStatus]]: "resolved"
  [[PromiseValue]]: Response

Promiseの中身を見る

Promiseとはなにかを考える前に、とりあえず先頭にawaitをつけて実行してみましょう。

await fetch("https://qiita.com/api/v2/items");

すると、こんな感じのオブジェクトが返ってきます。
Responseです。さっきよりは中がちょっと見えます。
例えば、ステータスコードが200であることがわかります。

Response {type: "basic", url: "https://qiita.com/api/v2/items", redirected: false, status: 200, ok: true, }
  type: "basic"
  url: "https://qiita.com/api/v2/items"
  redirected: false
  status: 200
  ok: true
  statusText: ""
  headers: Headers {}
  body: (...)
  bodyUsed: false
  __proto__: Response

bodyに値が入っていそうですが、クリックして展開してみるとReadableStreamと出てきます。
読み込めそうな何かだということしかわかりません。

body: ReadableStream;
locked: false;
__proto__: ReadableStream;

body の中身を見る

ReadableStreamのことは一旦忘れて、Responseについて調べてみましょう。
Response - Web API | MDNによると、arrayBuffer, blob, formData, json, textといったメソッドがあることがわかります。
今回の APIはJSONを返すことがわかっているので、jsonメソッドを使ってみましょう。

(await fetch("https://qiita.com/api/v2/items")).json();

ついにでデータまでたどり着くことができました。

(20) [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}]
lastIndex: (...)
lastItem: (...)
0: {rendered_body: "<h2>...</h2>", body:"...", ...}
1: {rendered_body: "<h2>...</h2>", body:"...", ...}
<<中略>>
19: {rendered_body: "<h2>...</h2>", body:"...", ...}
length: 20
__proto__: Array(0)

もう少し使ってみる

単にGETリクエストを送るだけではつまらないので、少しだけ深堀りしてみましょう。

ヘッダ

fetchの第2引数を使えば、好きなヘッダを付けてHTTPリクエスト送ることができます。

ヘッダ付きのHTTPリクエストを送るには下記のようにします。

await fetch(url, {
  headers: {
    Authorization: "Basic " + btoa("username" + ":" + "password"),
    Accept: "application/json",
    "Content-Type": "application/json;charset=utf-8"
  }
});

Headersオブジェクトを生成して送ることもできます。

var headers = new Headers();
headers.set("Authorization", "Basic " + btoa("username" + ":" + "password"));
await fetch(url, {
  headers
});

GET以外のリクエスト

fetchの第2引数を使えば、POSTリクエストやその他のリクエストを送ることができます。

dataに入っている文字列をそのままurlにPOSTで送るには、下記のようにします。

await fetch(url, {
  method: "POST",
  body: data
});

dataがJSONオブジェクトでurlにPOSTで送るには、下記のようにします。

await fetch(url, {
  method: "POST",
  body: JSON.stringify(data)
});

fetchのその他のオプション

詳しくはGlobalFetch.fetch() - Web API | MDNを見てください。
modecredentialcacheをよく使います。

F12ツール(DevTools)の特性

F12ツール(DevTools)は、とても良くできていてPromiseでも処理が終了していれば中身を見ることができます。[[PromiseValue]]となっているところに値が入っているので、クリックして展開して中身を見ることができます。

Promise {<pending>}
  __proto__: Promise
  [[PromiseStatus]]: "resolved"
  [[PromiseValue]]: Response
    type: "basic"
    url: "https://qiita.com/api/v2/items"
    redirected: false
    status: 200
    ok: true
    statusText: ""
    headers: Headers {}
    body: (...)
    bodyUsed: false
    __proto__: Response

awaitは、この[[PromiseValue]]を、処理が終了するまで待ってから取得するための言語機能です。

ReadableStream

Body - Web API | MDNによると、BodyはReponseへのmixinであり、Responseに対して arrayBuffer, blob, formData, json, textといったメソッドを提供するものです。

Body.bodyがReadableStreamですが、上記メソッドが呼ばれたときに変換する元データが入っている場所(元データを読み取り可能なストリーム)です。
ReadableStream - Web API | MDNに例があるように、ストリームを読み取りながら処理をしたり、読み取った前後になにか処理を実行したい場合に使うことができます。

Responseを扱うだけなら、Bodyのメソッドが覆い隠してくれるので、ReadableStreamを意識する必要はありません。

awaitを使わない方法

urlが文字列、optionsがオブジェクトのときawaitを使わずに同等の処理を書くなら下記のようになります。

fetch(url, options).then(response => {
  // このブロックの中ではPromiseではなくて、通常の値として扱える
  console.log(response);
  return response; // returnしてもPromiseに包まれる
});

実用性はあまりないですが、こう書いたり、

fetch(url, options).then(response => console.log(response));

こう書くこともできます。

fetch(url, options).then(console.log);

もっと使いたい方へ

もっと進んだ使い方をしたい場合は、下記の記事が参考になります。

参考

この記事を書くにあたり、下記の記事を参考にしました。

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

インストールMemo

[ステップ0]
sudo apt install nodejs npm
sudo npm instal -g n
n --stable
sudo npm install yarn

[lintとformat]
yarn add eslint prettier eslint-config-prettier eslint-plugin-prettier
.eslintrc
{
  "env": {
    "node": true,
    "browser": true,
    "es6": true
  },
  "extends": ["eslint:recommended", "plugin:prettier/recommended"],
  "rules": {
    "prettier/prettier": [
      "error",
      {
        "singleQuote": true,
        "trailingComma": "es5"
      }
    ]
  }
}
.pretteirrc
{
  "printWidth": 120,
  "tabWidth": 2,
  "singleQuote": true,
  "trailingComma": "none",
  "semi": false
}`

[vscode]settings.json
// ==== html ===========================================================
    "html.format.contentUnformatted": "pre, code, textarea, title, h1, h2, h3, h4, h5, h6, p", // フォーマットしないタグ
    "html.format.extraLiner": "", // headとbody要素の調節
    "html.format.preserveNewLines": false, // 指定タグ間にスペースを入れない
    "html.format.indentInnerHtml": true, // headとbodyをインデントする 
    "html.format.unformatted": null, // 再フォーマットしてはならないタグ 
    // === json
    "json.maxItemsComputed": 10000,
    // ==== javascript =====================================================
    "[javascript]": {
        "editor.tabSize": 2,
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    // === pretteir ========================================================
    "prettier.singleQuote": true,
    "prettier.tabWidth": 2,
    // ==== ESLint =========================================================
    "eslint.enable": true,
    "eslint.packageManager": "npm",
    "eslint.nodePath": "./node_modules",
    "editor.codeActionsOnSave": {
        "source.fixAll": true,
    },
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebStorageで一時データ管理をする

はじめに

クリスマスイブの妖精、大林雄一郎です。

もともとDartsLive200Sというおもちゃで練習用アプリをつくる予定だったんですが
自宅にある本体が壊れて端末に接続できない致命的な障害が発生したので買い直してまた次回のネタにします。

なので今回は即席感満載ですがWebStorageのお話です。

WebStorageとは

ググれば記事たくさんあるんですが
ローカル環境(ブラウザ)にデータをKeyValueで保存するための仕組みです。
データの編集や保存がJavaScriptでできるようになります。

今回はWebアプリで画面操作後の編集データをDB更新前まで保持して、都度画面表示に使用したいといった仕様で使いました。

WebStorageについて

これもググれば記事たくさんあるので省略しますが、簡単に書くと以下の種類と特徴があります。

■SessionStorage
 ・ブラウザ、タブ閉じるまで保存したデータを保持。
 ・Keyが同じでもブラウザ、タブ間では別データとして扱う。
 ・sessionて名前だけどサーバは関与しない。
  ⇒session生成元ごとに区切られた保存領域を使用している。らしい。

■LocalStorage
 ・任意に消すまで保存したデータを保持。
 ・Keyが同じである場合ブラウザ、タブ間では共通のデータとして扱う。
 ・ローカルマシンの領域にデータが保持される。

WebStorageいいこととわるいこと

■いいこと
・クライアントでデータ管理が完結する(サーバいらない)
・最近のブラウザであればなんでも使用可能
・jsなので開発者ツールでデータの状態が把握しやすい

■わるいいこと
・jsなのでXSS対策していないと危険
 ⇒個人情報や機密情報は保持しちゃだめ
・Localの場合、明示的に破棄しなければデータが残る
 ⇒管理はちゃんとしなきゃだめ

使用例~画面仕様~

↓みたいな画面で
image.png

緑枠GRIDのデータは行毎に↓みたいな子画面で登録
image.png

子画面での登録時に、緑枠GRIDに値を反映させる、といった画面挙動をします。
ただし子画面での登録ではDBへの反映はさせず、親画面の更新ボタンで一括でDBへ反映させるという仕様。

今回は以下の観点からSessionStorageを選択しています。
 ①運用上ブラウザで複数タブ開いて複数データを同時に参照する
 ②画面表示だけに使用できればよい

使用例~実装~

WebStorageメソッド・属性定義用js
getItem、setItem、removeItemくらいですかね使うの。

コードの冒頭でやっていますがuseSessionや
var storage = sessionStorage(localStorage)
にてsession or localの指定を行います。

var SaenaiWebStorage = {

    useSession: true,       // セッションストレージを使うか否か

    /**
     * Storageが使用可能かどうか
     */
    usable : function () {
        return (typeof localStorage !== 'undefined');
    },

    /**
     * ストレージを取得する
     *
     */
    getStorage : function (useSession) {
        return (useSession) ? sessionStorage : localStorage;
    },

    /**
     * 保存する
     *
     * @param key
     *              Key名
     * @param data
     *              データ
     */
    save : function (key, data) {
        SaenaiWebStorage.getStorage(this.useSession).setItem(key, JSON.stringify(data));
    },

    /**
     * 取得する
     *
     * @param key
     *              Key名
     */
    get : function (key) {
        var data = SaenaiWebStorage.getStorage(this.useSession).getItem(key);
        return (data == null) ? null : JSON.parse(data);
    },

    /**
     * 削除する
     *
     * @param key
     *              Key名
     */
    remove : function (key) {
        SaenaiWebStorage.getStorage(this.useSession).removeItem(key);
    },

    /**
     * 全てクリアする
     *
     */
    clear : function () {
        SaenaiWebStorage.getStorage(this.useSession).clear();
    }
}

画面別の保持データモデル
dataの内容がデータ保持対象の項目です。
functionは画面仕様に沿うことになり、data取得など個別の実装をしていくことになりますが、
基本的には↑のWebStorageメソッドを使用するものが最低限必要だと思います。

/**
 *×× データリポジトリ
 *
 * WebStorageに中間データを保持する
 *
 */
var SaenaiRepository = {

    keyName : 'Blessing',

    data: {
        SaenaiKishList: [],                 //冴えない会社リスト
        CircleNameList: [],                 // サークル名リスト
        combobox: [],                       // 名称表示用Combobox
        shinkanInfo: null                   // 高くて薄い本情報
    },

    restore: function() {
        var storageData = SaenaiWebStorage.get(this.keyName);
        if (storageData != null) {
            this.data = storageData;
        }
    },
    save: function() {
        SaenaiWebStorage.save(this.keyName, this.data);
    },
    isRestored : function() {
        return this.data.SaenaiKishList.length > 0;
    },
    initialize : function () {
        this.restore();
    },
    remove: function() {
        SaenaiWebStorage.remove(this.keyName);
        this.data = {
            SaenaiKishList: [],
            CircleNameList: [],
            combobox: [],
            shinkanInfo: null
        }
    },
    getCircleName : function(cd) {
        var item = getItemfromList(this.data.CircleNameList, 'xxxxx', cd);
        return item == null ? '': item.yyyyyy + '' + item.zzzzzz;
    },
    getSaenaiSikyKish: function (sikyKishKbn) {
        return getItemfromList(this.data.SaenaiKishList, 'kishKbn', kishKbn);
    },
    getComboboxItem: function (comboboxName, valueFieldName, value) {
        var list = this.data.combobox[comboboxName];
        return getItemfromList(list || [], valueFieldName, value);
    },
    getshinkanInfo: function(){
        return this.data.shinkanInfo;
    }
}

あとは画面でsaveしたい、removeしたい、getしたい場合のイベントでこれらを呼ぶだけで一時データ管理ができます。

最後に

大昔に同じような仕様でわざわざDBにGRID編集用のワークテーブルを作った黒歴史があったので
今回同じ過ちを繰り返すことがなくて本当によかったです(小並
他にもWebStorageの使いどころって何があるか考えてみようと思いました。

さーて素敵なイブのはじまりだネ☆

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

Tooltipの位置をSVG要素の位置を元に決める

はじめに

SVG 図を描いたときにマウスオーバーでツールチップを表示して付加的な情報を出したい、というのはよくありますよね。ツールチップも、SVG 要素に title を設定する簡易的な方法 からツールチップ用の div に対して visibility と position を設定する汎用的なものまであります。今回は後者の汎用的なものの話です。

D3.js で汎用的なツールチップを作る場合、ツールチップの位置を指定するのにマウスポインタの位置 (d3.event.page[XY]) を使う方法がよく紹介されています。たとえば Simple d3.js tooltips とか。でも、マウスポインタ位置でツールチップの位置を指定する方法だと、マウスオーバーするオブジェクトに対してツールチップの表示位置が都度変わってしまうんですよね。オブジェクトの右側にマウスを乗せれば右側に寄るし、左側にマウスを乗せれば左側による。マウスポインタ位置を使う以上どうしてもそうなる。

ここでは、マウスオーバーに対して、マウスポインタ位置に依存せずいつも決まった位置にツールチップを出したい = オブジェクトに対してツールチップ位置を固定する方法についてとりあげます。とはいってもこれもすでに整理してくれてる人がいるわけで、Positioning a Tooltip on an SVG がとても参考1 になります。ツールチップ作るだけならここのコードを元に適当にコピペするだけでも動くのですが、その中身で何をやっているのかを見てみましょう。

実例

Gitリポジトリの情報可視化 で実際に使っているので、これを元に話を進めます。こんな形で、rect の位置を元に、(マウスポインタ位置によらず) rect の真下にツールチップを出しています。

01.png

このツールチップをこの位置で出すためにどういう処理をしているのかを解説してみます。関係するコードを見てみましょう。

  _positionMatrixOfId(id) {
    const target = document.getElementById(id)
    // attribute .[xy] are only for rect and text.
    return target
      .getScreenCTM()
      .translate(+target.getAttribute('x'), +target.getAttribute('y'))
  }

  _enableTooltip(htmlStr, id) {
    const matrix = this._positionMatrixOfId(id)
    let yMargin = id.match(/commit.*label/) ? 1 : 2
    select('div#stat-tooltip')
      .style('visibility', 'visible')
      .style('left', `${window.pageXOffset + matrix.e - this.lc * 1.25}px`)
      .style('top', `${window.pageYOffset + matrix.f + this.lc * yMargin}px`)
      .html(htmlStr)
  }

_enableTooltip() はマウスオーバーで呼ばれる関数で、(1)ツールチップを visible にする (2)ツールチップの位置を設定する (3)ツールチップの中身(HTML)を設定する 処理をしています。そして (2)位置設定 では _positionMatrixOfId() が返す変換行列が使用されています。キモになるのはこの中にある getScreenCTM() ですね。

座標系の話

さて。getScreenCTM() の話をする前に、SVG 要素の位置情報を元にツールチップを出すために何をしなければいけないのかをおさらいしましょう。まずはマウスポインタ位置を使う方法について考えてみます。マウスポインタ位置を使う場合は具体的に「どこから見た」マウスポインタ位置をとっていたのか?

pageY についてみると

integer value in pixels for the y-coordinate of the mouse pointer, relative to the whole document

とあります。"ドキュメント全体" に対するマウスポインタの位置。つまり「ドキュメント全体の座標系で見て」どの位置にツールチップを出すべきかを決める必要がある。まあ図を見た方が早いので見てしまいましょう。上のコードではこういう座標変換をやっています。

02.png

具体的にやってみた方がわかりやすいので、Gitリポジトリの情報可視化 の実例をもとに座標値をとって上の図に入れています。順に見てみましょう。まず、ツールチップを表示させたい SVG 要素 (rect#stat190) は (32,16) の位置にあります。

03.png

このオブジェクトは SVG 左上を原点とする座標系から見て (259, 120) の位置に平行移動された座標系 (g#stat-group) の中にあります。

04.png

SVG 要素からわかるのはここまで。マウスオーバーが発生するのは rect ですが、オブジェクトにあるのは直近の座標系 (g#stats-group) から見たときの位置だけなので、どうにかしてこれを「ドキュメント全体の座標系」に変換してやらないといけないわけですね。

Screen Current Transformation Matrix (Screen CTM)

もう上の図でバラしてしまっていますが、getScreenCTM() はスクリーン (ディスプレイ上に見えている範囲) の原点を基準にして、指定した要素の属する座標系までの変換行列を出してくれます。

参照:

上の例で実際に計算してみましょう。

05.png

\begin{bmatrix}
a & c & e \\
b & d & f \\
0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
x \\
y \\
1
\end{bmatrix}
=
\begin{bmatrix}
1 & 0 & 267 \\
0 & 1 & 128 \\
0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
x \\
y \\
1
\end{bmatrix}
=
\begin{bmatrix}
x + 267 \\
y + 128 \\
1
\end{bmatrix}

getScreenCTM() は上で書いたような座標変換計算のための変換行列を 出しています。平行移動しかないので x/y にかかる係数 $a, b, c, d$ は使われておらず、使うのはオフセット部分 $e, f$ だけですね。さて。あとは svg の位置がどこになるかですが、

  • スクロールしていないのでドキュメント座標系とスクリーン座標系は一致 (window.page[XY]Offset = 0)
  • ドキュメント内の余白 (margin/padding) は bodymargin: 8px が設定されているだけ

06.png

という状態。後は足し算をしてやればよい。

  • pageXOffset + margin-left (body) + stats.transform.x = 0 + 8 + 259 = 267 = ScreenCTM.e
  • pageYOffset + margin-top (body) + stats.transform.y = 0 + 8 + 120 = 128 = ScreenCTM.f

rect の所属する座標系の原点までの変換行列 (平行移動なので x/y の移動量) が求められていることがわかります。実装上は

    return target
      .getScreenCTM() // screen 座養鶏で見た target (SVG要素) のある座標系の位置
      .translate(+target.getAttribute('x'), +target.getAttribute('y')) // target 位置まで平行移動

と、getScreenCTM のあとで translate して rect の位置を足し込んでいます。ここで返している変換行列はスクリーン座標系での rect の位置になるわけです。(それを下のコードでは matrix として受けている。)

    select('div#stat-tooltip')
      .style('visibility', 'visible')
      .style('left', `${window.pageXOffset + matrix.e - this.lc * 1.25}px`)
      .style('top', `${window.pageYOffset + matrix.f + this.lc * yMargin}px`)
    //               ^^^^^^^^^^^^^^^^^^^^ 
    //               ドキュメント座標系で見た スクリーン座標系の原点
    //               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //               ドキュメント座標系で見た 対象 SVG 要素 (rect) の位置

ドキュメント原点 → スクリーン原点 (page[XY]Offset) → 対象オブジェクト位置 ( matrix.[ef]) が求められています。それを起点にしているので、 ドキュメント全体の座標系で見たときの SVG 要素 (rect) 位置に対して どこにツールチップを表示するかを計算している……ということです。

おわりに

ツールチップの話でした。これも、どこから見た何の位置を求めるべきなのか、というのがちょっとわかりにくいですよね。まあグラフィクス関連は座標系の話からは逃れられないですが……。CTM についての説明はすごく単純な状況の計算だけ取り上げているので厳密にはちょっと違うとか説明が足りないとかはあるかも。変なところがあったら教えてください。


  1. この CodePen のデモは javascript - D3.js: Position tooltips using element position, not mouse position? - Stack Overflow で紹介されていたものです。 

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

【JS】appendについて

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

Railsでfullcalendarを使ってみる(Ajax通信でイベント登録)

概要

gemのfullcalndarを使ってイベントの登録をAjax通信でできるようにする。

  • カレンダーの日付をクリックするとイベント登録用のモーダルが表示
    image.png

  • 登録ボタンを押すと、イベントが登録される
    image.png

コード

  • jsファイル
    dayClickでカレンダーの日付をクリックした時のアクションを記述する。クリックした日付情報を取得し、イベント登録用のモーダルに自動入力させる。
calendar.js
$('#calendar').fullCalendar({
    events: '/shops/events.json',
    timeFormat: 'H:mm',
    eventColor: '#63ceef',
    lang: 'ja',
    dayClick: function (start, end, jsEvent, view) {
      //クリックした日付情報を取得
      const year = moment(start).year();
      const month = moment(start).month()+1; //1月が0のため+1する
      const day = moment(start).date();
      //イベント登録のためnewアクションを発火
      $.ajax({
        type: 'GET',
        url: '/shops/events/new',
      }).done(function (res) {
        //イベント登録用のhtmlを作成
        $('.modal-body').html(res);
        //イベント登録フォームの日付をクリックした日付とする
        $('#event_start_time_1i').val(year);
        $('#event_start_time_2i').val(month);
        $('#event_start_time_3i').val(day);
        //イベント登録フォームのモーダル表示
        $('#modal').modal();
        // 成功処理
      }).fail(function (result) {
        // 失敗処理
        alert('エラーが発生しました。運営に問い合わせてください。')
      });
    },

  });
  • イベントコントローラー
    render_to_stringを使って、イベント登録フォームのviewを文字列として取得し、モーダル上に表示する。
events_controller.rb
  def new
    @event = Event.new
    render plain: render_to_string(partial: 'form_new', layout: false, locals: { event: @event })
  end
  • イベント登録フォーム
_form_new.html.slim
h1 新規イベント追加
= form_with model: event, url: shops_events_path do |f|
  = render "devise/shared/error_messages", resource: f.object
  .form-group
    = f.label :title
    = f.text_field :title, class:'form-control mb-3', required: true
  .form-group
      = f.label :start_time
      br
      = f.datetime_select :start_time, {default: Date.today + 19.hours + 00.minutes, minute_step: 10}, class:'form-control bootstrap-date mb-3'
  .form-group
    = f.label :description
    = f.text_area :description, class:'form-control mb-3'
  .actions
    = f.submit class: "btn btn-primary mb-3 js-event-create-btn"
  • createアクション時の処理
    removeEventsの後、refetchEventsするとカレンダー上のイベントが再描画される。
create.js.slim
| $('#modal').modal('toggle');
| $("#calendar").fullCalendar('removeEvents');
| $("#calendar").fullCalendar('refetchEvents');

結論

fullcalendarを使ってAjax通信でイベント登録することができた。

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