20190323のJavaScriptに関する記事は24件です。

jQueryオブジェクトは$だけではないことを知っていますか?

こんにちは。俳句や川柳から動くGIF画像を生成できるWebサービス「五七五メーカー」をリリースしたアカネヤ(@ToshioAkaneya)です。

jQueryオブジェクトとは、ここでは有名な$のこととします。
$('.class-name')の様に使いますよね。

jQueryオブジェクトは$だけではないことを知っていますか?

大したことではないのですが、jQueryを導入すると、jQueryというオブジェクトもグローバルで定義されます。$ === jQueryはTrueです。

$は、ChromeのJS Consoleで定義済みの関数のため、そのサイトにjQueryが入っているか確かめるのにはjQueryが定義されているか確かめるのがおすすめです。

はてなブックマーク・Pocketはこちらから

はてなブックマークに追加
Pocketに追加

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

JavaScriptの基礎をやってみての感想

自己紹介

初めまして。aikig(アイキグ)(@aikig0926)と申します。
某情報系大学に在籍中の大学生です。

昨年9月から大学でプログラミングが始まり、そこからハマって春休み(1月下旬頃)から独学でプログラミングを勉強してます!
今の目標はインターンに参加してスキルアップがしたいです。よろしくお願いします。

スキルと経緯

大学でHTML/CSSの基礎の基礎やProcessingをやってました。HTML/CSSはさておき、Processingがグラフィック系が強く数学重視(座標概念が苦手)ということで挫折。
HTML/CSSをやりつつ何か無いかなと模索していた時にPHPカンファレンスに行ったことが転機でした。そこで初めてウェブ系言語を知り、そのあとHTML/CSSを経てJavaScriptをやり始めました。
春休みの最初(2月終わり)まではHTML/CSSをやってました。
JavaScriptを始めたのは3月からでJavaScriptって何それ???から始めてます。
Processingのときはあまりモチベ無かったんですが、JavaScriptやり始めてめっちゃ楽しい〜ってなってます。

やったこと

初心者独学マンの定番?ドットインストール。Progateもやってます。
最初ドットインストールだけでいいかなっていうノリで始めて、マジわけわからん(>_<)ってなったのでProgateから始めることを勧めます。

ここで一つ問題があってJavaScriptって最近バーションアップがあってProgateもドットインストールもレッスンの全てが最新版になってません。
Progateは特に学習コースのみで道場コースはないです。でも学習コースをやれば基礎はわかると思います。

・Progate JavaScriptES6 ⅴ以外 
基礎の基礎なので理解できるまでやることを勧めます。(オブジェクト概念とか初めてやる方は特に自分は3周くらいしました)

多くても3周くらいまでだと思います。ドットインストールなどでコードを書くうちに自然と理解します。
※ⅴはいらないかなーって感じでやってないです。はい。
個人差あると思いますがスライドがマジ神

・ドットインストール 
1 初めてのJavaScript
2 詳細JavaScript 基礎文法編
3 詳細JavaScript オブジェクト編
4 詳細JavaScript DOM編

やるレッスンはこの辺り。あとは個々のやりたい、作りたいものに応じてレッスンを進めていけばいいと思います。
自分はオブジェクト編が終わったあとDOM編をやらずにおみくじに入ったので反省してます。。。DOMを全く知らずにコードを書きまくるのはコスパ悪いです(笑)

期間としては大体20日くらい。レッスンが多いこととオブジェクト概念に苦しんだ結果だと思ってます。長かったです。。。

自分は初心者の定番?じゃんけんゲームを作りました。
最初はドットインストールのおみくじを作るレッスンをやって、そこからじゃんけんに。


こんな感じ。今はこのじゃんけんゲームをもっとレベルアップさせてます。

感想

確かにHTML/CSSよりはハードルが2段くらい上がる。
でも、実際にコードを書いて動くのは嬉しいし、いろんなことができるのはいい。
JavaScriptやるうちに自分がいかにグラフィックに向いてないかが分かりました(笑)

多分ですが、初心者の最初の壁がコマンド(命令)長くね?っていう問題とオブジェクト概念だと思います。

個人的な解決策ですが、コードを書きまくればどうにかなります。自分もゲームとかをドットインストールで作るうちになんとなくはわかったかな?って感じになりました。

完璧求めるとキリがないので5割程度でもOKという気持ちで。
重要なのは、コードを知ることと使い方を何となく知ることだと思います。
あとはひたすら実践! 簡単なものから自分のポートフォリオにしていくこと!!

1ヶ月くらいで基礎の基礎はできると思うので初心者の方もProgate→ドットインストールの順でやってみるといいと思います。

一回で理解できないと思いますが、(自分もそうだったし、今も途中)コードを書きまくるうちに理解できるので基礎の基礎を乗り切って自分のやりたい、作りたいプロダクトを作っていきましょう!
自分も苦しい時もありましたが、プログラミングが好き、〇〇を作りたい!って気持ちがあったらここまでこれたと思います。尊敬する某先生も言ってましたが初心者こそ情熱です!!

最後に

お読みいただきありがとうございました。初心者の方のお役に立てれば幸いです。

参考

ドットインストール
Progate

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

TypeScriptでnpmライブラリ開発ことはじめ

この記事では、わざわざTypeScriptでnpmライブラリ開発したいという読者を対象としています。

とはいえ今どきはVS Codeさんという素晴らしいエディターのおかげもあって、型が提供されていないnpmライブラリは使いづらい立場にあります。

では早速ですが、TypeScriptで"Hello だれだれ"とコンソールに表示されるnpmライブラリを開発してみましょう。

基本的なnpmライブラリ開発の知識

この記事では「TypeScriptで」npmライブラリ開発することに主眼を置きたいため、基本的なnpmライブラリ開発の知識は「本当にやさしいnpmライブラリ開発入門」くらいは知っている前提で進めます。

package.jsonのあれこれ

私の趣味は人の書いたpackage.jsonを見ることです。(趣味悪いでしょうw)

ではまずは今回お題にするpackage.jsonを見ていただきましょう。

package.json
{
  "name": "hello-world-typescript",
  "version": "1.0.0",
  "main": "dist/index.js",
  "license": "MIT",
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "tsc",
    "prepublishOnly": "npm run build"
  },
  "devDependencies": {
    "typescript": "^3.3.4000"
  }
}

とても綺麗ですよね。

ひとつひとつ意味があるので解説していきます。

package.json#name

npmライブラリの名前です。require('xxx')xxxにあたるアレです。

package.json#version

npmライブラリはこのバージョン情報を毎回インクリメントして公開する必要があります。

TypeScriptが一度3.3.3333というアレなバージョンをつけてしまったために、3.3.4にできずに3.3.4000になってしまったのは、笑えない笑える話として有名かもしれませんね。

package.json#main

ここに相対パスで有効なJavaScriptファイルが指定されていないとrequire('xxx')できません。
今回はTypeScriptのビルド結果を読み込ませたいので、dist/index.jsになっています。

package.json#files

package.jsonfilesプロパティは、使う人と使わない人がいると思います。
Webpackのincludeオプションとexcludeオプションに似ていて、前者はpackage.jsonfilesプロパティ、後者は.npmignoreです。

要はnpmはライブラリをnpm publishコマンドで公開する際に、filesプロパティが指定してあればそれらのみを公開対象とします。

TypeScriptはtscコマンドでビルドする前提なので、filesプロパティにビルド結果フォルダを指定してやると楽だよ、という話になるわけです。

package.json#scripts

npm run xxxを作れるプロパティですね。

npm run buildは実際にはtscコマンドを実行するところはみなさんお分かりかと思います。

prepublishOnlyがニッチなやつで、npm publishをする「前に」必ず実行されるという意味になります。
今回はnpm run buildを必ず走らせてからライブラリ公開したいので、これを指定しています。(開発したのにビルド忘れてバージョンしか上がらなかったという事故を防ぐやつです。)

ちなみに、scriptsプロパティにはいくつかのマジックがありまして、それが今回のpreにあたります。

例えばnpm run xxxを実行する場合、prexxxpostxxxを作成しておくと、それぞれ実行前と後に指定されたコマンドが叩かれたりします。
(なかなか便利なのですが駆使しすぎるとpackage.jsonの管理が大変になるので注意。)

tsconfig.json

お待たせしました。
みなさんこれが早く見たかったですよね。

ご査収ください。

tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "moduleResolution": "node",
    "strict": true,
    "skipLibCheck": true,
    "declaration": true,
    "pretty": true,
    "newLine": "lf",
    "outDir": "dist"
  }
}

ニッチなところだけ説明していきますね。

targetmoduleはどのようなライブラリの種類にするのか(Node.jsで利用されるのか、ブラウザで利用されるのか等)によって変わってきます。今回は最新のNode.jsで叩くだけのライブラリですのでtarget: "esnext"module: "commonjs"になります。Node.jsで利用される場合は常にmodule: "commonjs"と覚えておいても構いません。

せっかくなのでTypeScriptにきちんと仕事をさせたい。なのでstrict: trueにします。
でもライブラリのエラーが無限に出てくることがあるので、それは無視するためにskipLibCheck: trueにします。

型定義をきちんと出力したいので、declaration: trueにします。

あとWindowsで開発したい方は、クロスプラットフォームでの利用を念頭に改行コードをLFに寄せます。そのためにnewLine: "lf"します。
(例えばコマンドラインツール系のnpmライブラリをWindowsで開発した際に、Linux等で実行するとエラーになります。それを防ぐために改行コードをLFに寄せます。)

最後に今回はdistフォルダにコンパイル結果を出力したいので、outDir: "dist"とします。

TypeScriptでnpmライブラリ開発

やっと開発に入りますが、もう終わったようなものです。

src/index.ts
function hi(name: string) {
  console.log(`Hello ${name}`);
}

export default hi;

ビルドしてみます。

$ npm run build

distにビルド結果が出ました。

dist/index.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function hi(name) {
    console.log(`Hello ${name}`);
}
exports.default = hi;

型定義も出力されています。

dist/index.d.ts
declare function hi(name: string): void;
export default hi;

最後にnpmライブラリとして公開してみます。

$ npm publish

ちゃんとnpm run build(tsc)されてから公開されましたね。

まとめ

  • TypeScriptはコンパイル作業が必要なので、事前準備がひと手間かかる
  • それをクリアすればTypeScriptでもサクサクnpmライブラリ開発ができる!

いかがでしたでしょうか。
私は.NETやらTypeScriptやらでMicrosoftさんにはお世話になっていますが、型安全なのはとても気持ちいいです。

少しでも皆さまのお役に立てたらと思います。

では良いTypeScriptライフを!!

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

Qiitaユーザのつながりを可視化するWebページを作成してみた

はじめに

Qiitaでは、ユーザをフォローすることで、ユーザ間でつながりをもつことができます。今回は、指定したユーザの、フォローしているユーザとフォローされているユーザを、ネットワーク図で可視化するWebページを作成してみました。

作成物

ソースコード(HTMLとJavaScript)を、githubで公開しています。
https://github.com/t-mangoe/QiitaUserNetwork
作成したWebページには、以下からアクセスできます。
https://t-mangoe.github.io/QiitaUserNetwork/
Webページでは、以下の図のようにネットワーク図が表示されます。
Qiita可視化.jpg

処理内容

QiitaのWebAPIを叩いて、フォローしているユーザとフォローされているユーザの情報を取得します。この情報を基にvis.jsでネットワーク図を作成し、表示しています。

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

Node.jsのrequireの引数に変数を利用して動的に読み込む

環境

Node.js v10.13.0

本題

requireを使って他のモジュールやファイルを動的に読み込みたい場合、
変数をrequireの引数に利用することになる。

しかし、以下のようにすると上手くいかない。

上手くいかない.js
let path = 'path/foo';
require('./' + path);

require(...)とした場合には引数は静的である必要があるらしい。

なので、以下のようにコールバックを利用して動的な読み込みをおこなう。

正しく動く.js
let path = './path/foo';
require([path], function (foo)
    // path/fooモジュールが変数fooにロードされている
);

コールバックなので若干使い勝手は悪いけれど、
条件によって読み込むモジュールを変更できるのは便利。

参考文献

参考文献というか、公式サイトに上の内容がちゃんと書かれていた...

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

アップロード前に画像を非同期でプレビューする 解説・備忘録(jQuery)

どんな内容?

こんにちは!
Rails開発六ヶ月の卵エンジニアです!

ちょっと前にアイコン画像を非同期でプレビューさせるときにこちらを参考にして実装したので、
今回はその備忘録を兼ねて、理屈を解説しつつ、ここに保存しておこうと思います。

(結局MDNのFileReaderを読み込めば実装できるので、正確な学習をするならこっち見た方がいいかも)

私と同じような駆け出しの仲間へ、何かしらの助けになれば幸いです。

ソースコード

コードは参照したサイトのものをほぼそのまま。
JSFiddleにも記述しておきましたので、
よろしければ挙動確認はこちらをお使いください。

preview.js
function readURL(input) {

  if (input.files && input.files[0]) {
    var reader = new FileReader();

    reader.onload = function(e) {
      $('#imgPre').attr('src', e.target.result);
    }
    reader.readAsDataURL(input.files[0]);
  }
}

$("#imgInp").change(function() {
  readURL(this);
});
preview.html
<!-- jQueryの読み込み すでに導入してあれば不要 -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>


<form>
  <input type='file' id="imgInp" />
  <img id="imgPre" src="#" alt="your image" />
</form>

処理の流れ

要するに
1 inputタグで選択した画像のパスを、
2 プレビューしたいimgタグのsrcに代入し、
3 ブラウザ側からsrcを元に画像を読み込ませる。

という流れになります。

記述の解説

① inputタグを用意・イベント発火

ファイルアップロードのため、inputタグを用意します。

preview.html
<input type='file' id="imgInp" />

今回は画像を選択した際にプレビューを表示させたいので、
inputタグの中身が変更されたタイミングで処理が発火されるようにします。

preview.js
$("#imgInp").change(function() {
  readURL(this);
});

➁ ③ プレビューしたいimgタグ用意・プレビューさせる

プレビューしたい場所にimgタグを置いておきます

preview.html
  <img id="imgPre" src="#" alt="your image" />

①のpreview.jsで発火するreadURL関数の中身、
プレビュー処理を記述します。

preview.js
function readURL(input) {

  if (input.files && input.files[0]) {
    var reader = new FileReader();

    reader.onload = function(e) {
      $('#imgPre').attr('src', e.target.result);
    }
    reader.readAsDataURL(input.files[0]);
  }
}

FileReaderオブジェクトについてはこちらを。
FileReaderはFileオブジェクトを使用してユーザーのコンピュータから非同期でファイルを読み込みます。
Fileオブジェクトはinput要素でファイルを選択した際に得られるFileListオブジェクト(input.files)内に。
今回はその先頭の(input.file[0])を指定して、プレビュー対象のFileオブジェクトを選択しています。

readAsDataURLメソッドを使い、ユーザーが入力したFileオブジェクト(input.file[0])を読み込みます。

preview.js
reader.readAsDataURL(input.files[0])

reader.onloadプロパティに処理を記述します。
そうすることでreadAsDataURLメソッドが完了すると発火するloadイベント時にここが実行されます。

プレビューしたいimgタグのsrcにURLを代入

preview.js
    reader.onload = function(e) {
      $('#imgPre').attr('src', e.target.result);
    }

これで完了です。

まとめ

いかがでしたでしょうか。

FileReaderオブシェクトの使い方さえわかっていれば特に難しく考えずに実装できるのかなと思います。

基本的にコピペすればそのまま動くと思いますが、
機能を拡張したくなった時や、応用する時に理屈を知ってないと難しいですね。

技術解説は初めてなので、至らぬ点や、間違った表現・解説をしていた場合は、ぜひお知らせください。

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

DataTablesで合計値を表示する

はじめに

業務系のシステムでは表組みで情報を表示することがよくありますが、そんな時に便利なのがDataTablesです。
ページングやソートといった、よくあるけど自前で用意すると地味に手間な機能が用意されているのでとても助かります。
https://datatables.net

そんなDataTablesを利用して集計を行った場合に、合計値をDataTablesの外で表示するための備忘録です。(合計値以外でも可能ですね)

Footer callback

まずはDataTables内で表示するパターンです。
公式サイトの実装例通り、footerCallback関数を利用します。
https://datatables.net/examples/advanced_init/footer_callback.html

sample.html
$(document).ready(function() {
    $('#example').DataTable( {
        "footerCallback": function ( row, data, start, end, display ) {
            var api = this.api(), data;

            // Remove the formatting to get integer data for summation
            var intVal = function ( i ) {
                return typeof i === 'string' ?
                    i.replace(/[\$,]/g, '')*1 :
                    typeof i === 'number' ?
                        i : 0;
            };

            // Money delimiter
            var moneyVal = function (i) {
                var valAry
                valAry = String(i).split('.')
                valAry[0] = valAry[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,')
                return valAry.join('.')
            };

            // Total over all pages
            total = api
                .column( 4 )
                .data()
                .reduce( function (a, b) {
                    return intVal(a) + intVal(b);
                }, 0 );

            // Total over this page
            pageTotal = api
                .column( 4, { page: 'current'} )
                .data()
                .reduce( function (a, b) {
                    return intVal(a) + intVal(b);
                }, 0 );

            // Update footer
            $( api.column( 4 ).footer() ).html(
                '$'+ moneyVal(pageTotal) +' ( $'+ moneyVal(total) +' total)'
            );
        }
    } );
} );

Draw callback

次に本題のDataTables外で表示するパターンですが…こちらも公式サイトのReferenceに載っています。
drawCallback関数を利用し、DOM操作で結果を反映します。
https://datatables.net/reference/option/drawCallback

例えば<p id="total"></p>という合計値の表示領域があったとすると、下記のようになります。

sample.html
$(document).ready(function() {
    $('#example').DataTable( {
        "drawCallback": function( settings ) {
            var api = this.api();

            // Remove the formatting to get integer data for summation
            var intVal = function ( i ) {
                return typeof i === 'string' ?
                    i.replace(/[\$,]/g, '')*1 :
                    typeof i === 'number' ?
                        i : 0;
            };

            // Money delimiter
            var moneyVal = function (i) {
                var valAry
                valAry = String(i).split('.')
                valAry[0] = valAry[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,')
                return valAry.join('.')
            };

            // Total over all pages
            total = api
                .column( 4 )
                .data()
                .reduce( function (a, b) {
                    return intVal(a) + intVal(b);
                }, 0 );

            // Total over this page
            pageTotal = api
                .column( 4, { page: 'current'} )
                .data()
                .reduce( function (a, b) {
                    return intVal(a) + intVal(b);
                }, 0 );

            // Update value
            window.$('#total').html('$'+moneyVal(pageTotal) +' ( $'+ moneyVal(total) +' total)');
        }
    } );
} );

footerCallback関数とほとんど同じですね。

おわりに

DataTables、便利です。

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

公式ドキュメントリンク集(JS,API)

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

公式ドキュメントリンク集(Python,JS,API)

延々と足して行く。増えたら分類する

Dev tools(ざっくりだな。。)

名称 説明 URL
git 使いにくい https://git-scm.com/doc
vagrant virtualbox上にたてて使ってる https://www.vagrantup.com/docs/
docker 負荷試験ぐらいでしか使えてない https://docs.docker.com/

Python

名称 説明 URL
pip パッケージ https://pip.pypa.io/en/stable/
pycurl curlできる http://pycurl.io/docs/latest/
hashlib CTFでよくつかう https://docs.python.org/ja/3/library/hashlib.html

JavaScript

名称 説明 URL
npm パッケージ https://docs.npmjs.com/
yarn パッケージ https://yarnpkg.com/lang/en/docs/
React コンポ https://reactjs.org/docs/getting-started.html
AngularJS https://docs.angularjs.org/guide
Babel なまえすごい https://babeljs.io/docs/en/
gulp ビルド https://gulpjs.org/
browserify https://github.com/browserify/browserify#usage

Elastic Stack

名称 説明 URL
Elasticsearch 検索 https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
Logstash 処理 https://www.elastic.co/guide/en/logstash/current/index.html
Kibana 表示 https://www.elastic.co/guide/en/kibana/current/index.html

REST API

名称 説明 URL
Virustotal マルウェアのあれ https://www.virustotal.com/ja/documentation/public-api/

Game

名称 説明 URL
cocos2d-x ゲームエンジン https://docs.cocos2d-x.org/api-ref/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

公式ドキュメントリンク集(Python,JS,API,Security)

延々と足して行く。増えたら分類する

Dev tools(ざっくりだな。。)

名称 説明 URL
git 使いにくい https://git-scm.com/doc
vagrant virtualbox上にたてて使ってる https://www.vagrantup.com/docs/
docker 負荷試験ぐらいでしか使えてない https://docs.docker.com/

Python

名称 説明 URL
pip パッケージ https://pip.pypa.io/en/stable/
pycurl curlできる http://pycurl.io/docs/latest/
hashlib CTFでよくつかう https://docs.python.org/ja/3/library/hashlib.html

JavaScript

名称 説明 URL
npm パッケージ https://docs.npmjs.com/
yarn パッケージ https://yarnpkg.com/lang/en/docs/
React コンポ https://reactjs.org/docs/getting-started.html
AngularJS https://docs.angularjs.org/guide
Babel なまえすごい https://babeljs.io/docs/en/
gulp ビルド https://gulpjs.org/
browserify https://github.com/browserify/browserify#usage

Elastic Stack

名称 説明 URL
Elasticsearch 検索 https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
Logstash 処理 https://www.elastic.co/guide/en/logstash/current/index.html
Kibana 表示 https://www.elastic.co/guide/en/kibana/current/index.html

REST API

名称 説明 URL
Virustotal マルウェアのあれ https://www.virustotal.com/ja/documentation/public-api/

Security

名称 説明 URL
pfSense OSSのFW/router https://docs.netgate.com/pfsense/en/latest/index.html
VyOS OSSのrouter https://wiki.vyos.net/wiki/User_Guide

Game

名称 説明 URL
cocos2d-x ゲームエンジン https://docs.cocos2d-x.org/api-ref/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

setterとgetterの実装

JavaScript本格入門(ISBN 978-4774184111)で基礎からJavaScriptを勉強するシリーズです。今回はChapter5からアクセサーメソッドについてです。

アクセサーメソッドは、セッターメソッドとゲッターメソッドを合わせた言い方です。
アクセサーメソッドの実装の仕方と使用方法について勉強したのでまとめます。

アクセサーメソッドを使わない実装

let human = {
    name:"",
    age:"",
}

// 使う側(値のセット)
human.name = "Giorno Giovanna";
human.age = 15;

// 使う側(値のゲット)
console.log(human.name);  // Giorno Giovanna
console.log(human.age);   // 15

非常にシンプルな実装です。
続いてアクセサーメソッドを用いた実装をしてみます。

アクセサーメソッドを使った実装

let human = {
    set name(value){
        this._name = value;
    },
    get name(){
        return this._name;
    },
    set age(value){
        this._age = value;
    },
    get age(){
        return this._age;
    }
}

// 使う側(値のセット)
human.name = "Giorno Giovanna";
human.age = 15;

// 使う側(値のゲット)
console.log(human.name);  // Giorno Giovanna
console.log(human.age);   // 15

humanオブジェクトの中にset構文get構文を用いてプロパティを定義してみました。

まずセッターメソッドの動きを見てみます。
1. human.nameに値をセットすると、セッターメソッドset name(value)が呼ばれます
2. セッターメソッドの中では、human._nameというプロパティを新しく作成し、そこにvalueの値を格納します

使う側は、human.nameに値を代入したつもりなのですが、セッターメソッドの動きによって値をhuman._nameという別のところに格納したことになります。

次にゲッターメソッドの動きを見てみます。
1. human.nameにアクセスしようとすると、ゲッターメソッドget name()が呼ばれます
2. ゲッターメソッドの中では、human._nameというプロパティの値を戻り値として返します

使う側は、human.nameの値を取得したつもりなのですが、ゲッターメソッドの動きによってhuman._nameという別の値を取得させられます。

セッターメソッドとゲッターメソッドの働きによって、間接的にhuman._nameの値をやり取りするので、使う側からはhumanオブジェクトの中身がアクセサーメソッドを使って実装されてあろうがなかろうが、値のセットとゲットの方法は変わらないことに注目です。

値のセットとゲットにワンクッションあるだけですので、これではあまり何がうれしいのかわかりませんので、セッターとゲッターを使うと便利になる例を紹介します。

例1: プロパティに格納する値のチェックをしたい場合

human.nameに入る値は文字列でないといけないし、human.ageは数値であるべきです。
そのような場合、セッターメソッドにチェックする実装を入れてやることができます。

before
let human = {
    name:"",
    age:"",
}

// 使う側(値のセット)
human.name = 15;                   // 望ましくない値として数値が入ってきた
human.age = "Giorno Giovanna";     // 望ましくない値として文字列が入ってきた

// 使う側(値のゲット)
console.log(human.name);  // 15
console.log(human.age);   // Giorno Giovanna

望ましくない値がそのままプロパティにセットされてしまいます。

after
let human = {
    set name(value){
        if(typeof value === "string"){
            this._name = value;
        }else{
            console.log("nameが文字列じゃないじゃあないか!");
        }
    },
    get name(){
        return this._name;
    },
    set age(value){
        if(typeof value === "number"){
            this._age = value;
        }else{
            console.log("ageが数値じゃないじゃあないか!");
        }
    },
    get age(){
        return this._age;
    }
}

// 使う側(値のセット)
human.name = 15;
human.age = "Giorno Giovanna";

// 使う側(値のゲット)
console.log(human.name);  // undefined
console.log(human.age);   // undefined
実行結果
nameが文字列じゃないじゃあないか!
ageが数値じゃないじゃあないか!
undefined
undefined

セッターメソッドで値のチェックをすることができ、使う側が幾分か安全になりました。

例2: プロパティを読み取り専用にしたい場合

年齢は毎年変わりますが、名前はそう簡単には変わるものではありません。

なので、human.ageは後から書き換え可能にしたいですが、human.nameは後から変えられないように読み取り専用にしたいとします。

before
let human = {
    name: "Giorno Giovanna",  // もともとの名前
    age: 15,                  // もともとの年齢
}

// 使う側(値のゲット)
console.log(human.name);  // Giorno Giovanna
console.log(human.age);   // 15

// 使う側(値のセット)
human.name = "汐華初流乃";    // 改名!
human.age = 16;              // 加齢!

// 使う側(値のゲット)
console.log(human.name);  // 汐華初流乃(改名されてしまった。望ましくない)
console.log(human.age);   // 加齢された(これは望ましい)

これではhuman.nameの値が後から書き換えられてしまいます。
アクセサーメソッドを使ってhuman.nameを読み取り専用にしてみます。

after
let human = {
    _name : "Giorno Giovanna",  // もともとの名前
    _age : 15,                  // もともとの年齢
    get name(){
        return this._name;
    },
    set age(value){
        this._age = value;
    },
    get age(){
        return this._age;
    }
}

// 使う側(値のゲット)
console.log(human.name);  // Giorno Giovanna
console.log(human.age);   // 15

// 使う側(値のセット)
human.name = "汐華初流乃";    // 改名!
human.age = 16;              // 加齢!

// 使う側(値のゲット)
console.log(human.name);  // Giorno Giovanna(改名が阻止できた)
console.log(human.age);   // 加齢された(これは望ましい)

改名が阻止できました。
何をしたかというと、nameのセッターメソッドの実装をやめゲッターメソッドだけ持つようにするようにしました。これにより、human.nameは読み取り専用となりました。
human.ageは後で加齢できるように読み書きできるようにセッターメソッド、ゲッターメソッドの両方を持ちます。

まとめ

セッターメソッドとゲッターメソッドを使うことで、
1. プロパティに予期しない値が入るのを防ぐこと
2. プロパティの値が後から書き換わることを防ぐこと
ができることがわかりました。

ただし、この実装では限界があり、使う側が直接human._namehuman._ageをアクセスしてしまうと、1、2が保証できなくなってしまいます。

コーディング規約などでアンダースコア付きのプロパティには直接アクセスしないことをお約束しておく必要があります。

おまけ

クロージャやシンボルを使うともう少し安全なアクセサーメソッドを実装することができます。

以下の記事で紹介されているものがわかりやすいです。
JavaScriptのgetter/setterの使い方を考えよう

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

argumentsプロパティの理解と使用例。

argumentsプロパティたるものと、その使用例を、感覚的に理解するためのメモ。
【前提知識】for...of 文

argumentsとは?

  • 実引数のこと。
  • 関数を呼び出す際に入力される引数のこと。
  • 実引数は、関数のargumentsというプロパティに、配列の形で代入されている。
    • 例:第一引数 → arguments[0]

【使用例】

使用例.js
const xxx = ()=> {
  for ( let index in xxx.arguments ) {
    console.log ( xxx.arguments[index] );
  }
};
//これで、渡された引数をすべてconsoleに表示することができる。
//map関数の引数バージョンのようなイメージ。

xxx(1,2,3,4,5); 
//1
//2
//3
//4
//5
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

独自の音声圧縮をJavaScriptで作ってみた

はじめに

今回はMP3、Vorbis(Ogg)、 AAC等の音声圧縮形式が使用している修正離散コサイン変換を使用して音声データの圧縮、伸長をJavaScriptとWebAudioを使用して実装して見ました。

音声圧縮用のWebアプリケーション

https://redlily.github.io/training-webaudio-compression

設計

設計方針

  • JavaScriptとWebAudioで圧縮、伸長、再生が可能な実装を行う。
  • 音声に関わる実装はOSSのライブラリを一切使わずに自力で実装する。
  • MP3、Vorbis(Ogg)、AACの音声圧縮形式が使用している修正離散コサイン変換を用いた実装を行う。
  • 音質をある程度保ちながら16bitで量子化された無圧縮データと比較して1/8程度まで圧縮を行う。
  • なるだけ単純な実装で圧縮率を上げる。(エンコーダ、デコーダの実装をあわせて1000行程度)
  • 今回は音声圧縮のノウハウの獲得が目的なので可逆圧縮の導入は見送り。

圧縮・再生用のアプリケーションの設計

音声の読み込み、圧縮、伸長、再生を行うアプリケーション部分の設計概要となります。

圧縮

圧縮のイメージ

  1. 音声データを読み込む
  2. AudioContex.decodeAudioを使用して音声データを波形データに変換する。
  3. 波形データを独自の圧縮形式にエンコードする。
  4. ArrayBufferにデータを変換し、ダウンロード可能にする。

伸長、再生

伸長、再生のイメージ

  1. 独自の圧縮済みの音声データを読み込む。
  2. ScriptProcessorNodeを生成する。
  3. 独自の圧縮済み音声データを波形データにデコードする。
  4. 波形データをスピーカーに出力する。

エンコーダ・デコーダの設計

音声の波形データを実際にエンコード、デコードするコーデックの設計概要となります。

エンコード

エンコードのイメージ

  1. 波形データを入力
  2. 窓関数を適用
  3. 修正離散コサイン変換を適用
  4. 周波数を間引く
  5. 圧縮済みデータを出力

デコード

デコードのイメージ

  1. 圧縮済みデータを入力
  2. 間引かれた周波数データを修正離散コサイン変換用の配列に展開
  3. 逆修正離散コサイン変換を適用
  4. 窓関数を適用
  5. 波形データを出力

データ設計

基本構造

大枠の設計はヘッダーがデータの先頭あり、その後に固定長のフレームの配列が連なるといった単純な構造となります。

データ構造のイメージ

ヘッダ

変数名 説明
MAGIC_NUMBER UINT32 マジックナンバー、"WAM0"が固定値
DATA_SIZE UINT32 データのバイトサイズ
DATA_TYPE UINT32 拡張用のデータタイプ、"SMD0"が固定値
VERSION UINT32 データのバージョン
SAMPLING_RATE UINT32 サンプリングレート
CHANNEL_SIZE UINT32 チャネル数、1がモノラル、2がステレオ
SAMPLE_COUNT UINT32 データに含まれるサンプル数
FREQUENCY_RANGE UINT32 周波数ブロックのサイズ
FREQUENCY_TABLE_SIZE UINT32 周波数テーブルのサイズ
FRAME_COUNT UINT32 データに含まれるフレーム数
FRAME_DATA FRAME[CHANNEL_SIZE * FRAME_COUNT] フレーム配列、チャネル数が2の場合、左、右とフーレムが並ぶ

フレーム

変数名 説明
MASTER_SCALE UINT32 このフーレムの主音量
SUB_SCALES UINT4[8] 8つの周波数帯用の音量を調整するためのスケール値
ENABLE_FREQUENCIES 1bit[FREQUENCY_RANGE]
or
log_2(FREQUENCY_RANGE)-bit[FREQUENCY_TABLE_SIZE]
周波数の有効無効を収納した1bitのフラグ配列、もしくは有効な周波数のインデックスを収納した配列
バイト数の小さい方を使用し4バイトアライメントに適合するサイズにする
FREQUENCY_VALUES 4bit[FREQUENCY_TABLE_SIZE] 有効な周波数の対数で符号化された数値

離散コサイン変換(discrete cosine transform)

N個のサンプルデータに対してN個の0からN-1の異なる周波数のcos波に信号を分解する事が出来る変換になります。

JPEGはこの離散コサイン変換を利用してデータの圧縮を行っています。

下記はイメージ図となります。

DCTのイメージ

離散コサイン変換タイプ2(DCT-Ⅱ もしくは DCT)の式

y_k = \sum^{N-1}_{n=0} x_n \cos(\frac{\pi}{N}(n + \frac{1}{2})k)

離散コサイン変換タイプ3(DCT-Ⅲ もしくは 逆DCT)の式

x_n = \frac{1}{2} y_0 + \sum^{N-1}_{k=1} y_k \cos(\frac{\pi}{N}(n + \frac{1}{2})k) 

高速化

離散コサイン変換はN^2のオーダーで計算量が増えてしまう特性があります。そのまま離散コサイン変換を数式通り実装してしまうとかなり遅くなってしまうので高速化アルゴリズムを導入します。

今回はLee型DCTと呼ばれる高速化手法を下記の論文を参考に実装しました。
これを適用する事によりN log Nのオーダーの計算量ですみます。

A New Algorithm to Compute the Discrete Cosine Transform - BYEONG GI LEE

離散コサイン変換タイプ2(DCT-Ⅱ)の高速化の実装例

// 離散コサイン変換、タイプII
// n - サンプル数、2のべき乗である必要がある
// x - n個のサンプルの配列
static dctII(n, x) {
    // バタフライ演算
    let rad = Math.PI / (n << 1);
    for (let m = n, mh = m >> 1; 1 < m; m = mh, mh >>= 1) {
        for (let i = 0; i < mh; ++i) {
            let cs = 2.0 * Math.cos(rad * ((i << 1) + 1));
            for (let j = i, k = (m - 1) - i; j < n; j += m, k += m) {
                let x0 = x[j];
                let x1 = x[k];
                x[j] = x0 + x1;
                x[k] = (x0 - x1) * cs;
            }
        }
        rad *= 2.0;
    }

    // データの入れ替え
    FastDCT.swapElements(n, x);

    // 差分方程式
    for (let m = n, mh = m >> 1, mq = mh >> 1; 2 < m; m = mh, mh = mq, mq >>= 1) {
        for (let i = mq + mh; i < m; ++i) {
            let xt = (x[i] = -x[i] - x[i - mh]);
            for (let j = i + mh; j < n; j += m) {
                let k = j + mh;
                xt = (x[j] -= xt);
                xt = (x[k] = -x[k] - xt);
            }
        }
    }

    // スケーリング
    for (let i = 1; i < n; ++i) {
        x[i] *= 0.5;
    }
}

離散コサイン変換タイプ3(DCT-Ⅲ)の高速化の実装例

// 離散コサイン変換、タイプIII
// n - サンプル数、2のべき乗である必要がある
// x - n個のサンプルの配列
static dctIII(n, x) {
    // スケーリング
    x[0] *= 0.5;

    // 差分方程式
    for (let m = 4, mh = 2, mq = 1; m <= n; mq = mh, mh = m, m <<= 1) {
        for (let i = n - mq; i < n; ++i) {
            let j = i;
            while (m < j) {
                let k = j - mh;
                x[j] = -x[j] - x[k];
                x[k] += x[j = k - mh];
            }
            x[j] = -x[j] - x[j - mh];
        }
    }

    // データの入れ替え
    FastDCT.swapElements(n, x);

    // バタフライ演算
    let rad = Math.PI / 2.0;
    for (let m = 2, mh = 1; m <= n; mh = m, m <<= 1) {
        rad *= 0.5;
        for (let i = 0; i < mh; ++i) {
            let cs = 2.0 * Math.cos(rad * ((i << 1) + 1));
            for (let j = i, k = (m - 1) - i; j < n; j += m, k += m) {
                let x0 = x[j];
                let x1 = x[k] / cs;
                x[j] = x0 + x1;
                x[k] = x0 - x1;
            }
        }
    }
}

離散コサイン変換の欠点

離散コサイン変換を音声圧縮に使用した場合、サンプルを適当なサイズのブロックに区切って周波数変換をかけて、そのデータに対し非可逆圧縮をかけていく事になるのですが、この方法で非可逆圧縮をかけて元に戻そうとした場合、ブロックとブロックの境で高周波数のノイズが発生してしまいます。

音声に対してDCT

この問題は同じく離散コサイン変換を使用するJPEGでもブロックノイズという形で発生します。音声の場合は聞くに耐えないくらいのノイズが発生するので、この問題に対処する必要があります。

修正離散コサイン変換(modified discrete cosine transform)

離散コサイン変換を音声に適応する上での欠点、変換したブロックとブロックの境界で高周波数のノイズが発生してしまう欠点を窓関数と組み合わせて使用する事により、その欠点を解消することの出来る変換となります。

具体的にはこの変換は解析ブロック半分ずつ重ねて周波数変換を行うことが出来、この重なる部分に対して窓関数をかけてクロスフェードさせる事により解析ブロックと継ぎ目を目立たなくすることが出来ます。

音声に対してMDCT

修正離散コサイン変換(MDCT)の式

y_k = \sum^{2N-1}_{n=0} x_n \cos(\frac{\pi}{N}(n + \frac{1}{2} + \frac{N}{2})(k + \frac{1}{2})) 

逆修正離散コサイン変換(逆MDCT)の式

x_n = \frac{1}{N} \sum^{N-1}_{k=0} y_k \cos(\frac{\pi}{N}(n + \frac{1}{2} + \frac{N}{2})(k + \frac{1}{2}))

高速化

今回は離散コサイン変換の高速化処理をそのまま流用できるアルゴリズムを下記の論文を参考に実装しました。

Fast IMDCT and MDCT Algorithms— A Matrix Approach Mu-Huo Cheng and Yu-Hsin Hsu

修正離散コサイン変換の高速化の実装例

// 修正コサイン変換(MDCT)
// n - 周波数配列数、2のべき乗である必要がある
// samples - 2n個のサンプル配列、この配列が変換処理の入力元となる
// frequencies - n個の周波数配列、この配列が変換処理の出力先となる
static mdct(n, samples, frequencies) {
    // データを結合
    let ns1 = n - 1;            // n - 1
    let nd2 = n >> 1;           // n / 2
    let nm3d4 = n + nd2;        // n * 3 / 4
    let nm3d4s1 = nm3d4 - 1;    // n * 3 / 4 - 1
    for (let i = 0; i < nd2; ++i) {
        frequencies[i] = samples[nm3d4 + i] + samples[nm3d4s1 - i];
        frequencies[nd2 + i] = samples[i] - samples[ns1 - i];
    }

    // cos値の変換用の係数をかけ合わせ
    let rad = Math.PI / (n << 2);
    let i = 0;
    let nh = n >> 1;
    for (; i < nh; ++i) {
        frequencies[i] /= -2.0 * Math.cos(rad * ((i << 1) + 1));
    }
    for (; i < n; ++i) {
        frequencies[i] /= 2.0 * Math.cos(rad * ((i << 1) + 1));
    }

    // DCT-II
    FastDCT.dctII(n, frequencies);

    // 差分方程式
    for (let i = 0, j = 1; j < n; i = j++) {
        frequencies[i] += frequencies[j];
    }
}

逆修正離散コサイン変換(Inverse MDCT)の高速化の実装例

// 逆修正コサイン変換
// n - 周波数配列数、2のべき乗である必要がある
// samples - 2n個のサンプル配列、この配列が変換処理の出力先となる
// frequencies - n個の周波数配列、この配列が変換処理の入力元となる
static imdct(n, samples, frequencies) {
    // cos値の変換用係数を掛け合わせ
    let rad = Math.PI / (n << 2);
    for (let i = 0; i < n; ++i) {
        frequencies[i] *= 2.0 * Math.cos(rad * ((i << 1) + 1));
    }

    // DCT-II
    FastDCT.dctII(n, frequencies);

    // 差分方程式
    frequencies[0] *= 0.5;
    let i = 0, j = 1;
    let nh = n >> 1;
    for (; i < nh; i = j++) {
        frequencies[j] += (frequencies[i] = -frequencies[i]);
    }
    for (; j < n; i = j++) {
        frequencies[j] -= frequencies[i];
    }

    // スケーリング
    for (let j = 0; j < n; ++j) {
        frequencies[j] /= n;
    }

    // データを分離
    let ns1 = n - 1;            // n - 1
    let nd2 = n >> 1;           // n / 2
    let nm3d4 = n + nd2;        // n * 3 / 4
    let nm3d4s1 = nm3d4 - 1;    // n * 3 / 4 - 1
    for (let i = 0; i < nd2; ++i) {
        samples[ns1 - i] = -(samples[i] = frequencies[nd2 + i]);
        samples[nm3d4 + i] = (samples[nm3d4s1 - i] = frequencies[i]);
    }
}

窓関数(windows function)

修正離散コサイン変換のデータの重なった部分を滑らかに結合するために使用します。簡単に言えばクロスフェードさせてデータのブロックとブロックの境を目立たなくするために導入する関数となります。

窓関数による重ね合わせのイメージ

今回はVorbisで使用されているVorbis窓と呼ばれる窓を使用します。

\omega_x = \sin(\frac{\pi}{2}\sin^{2}\pi x), 0 \leq x \leq 1 

周波数帯の間引き

人間の耳は相対的に大きな音の近くにある周波数帯の小さな音は感知しにくい特性があります。これをマスキング効果いい、これを利用して修正離散コサイン変換で得られた周波数配列からデータを間引きます。

アルゴリズムとしては等ラウドネス曲線等を利用して各周波数の音量を人間の知覚的に平準化、その後マスキング効果による周波数の選定を行えば、より良い結果が得られるのでしょうが、今回は実装は割愛してわかりやすく周波数帯を適当にぶつ切りにして、そのぶつ切りにした周波数帯の中で最も数値の絶対値が大きい周波数を出力候補としました。

周波数の間引のイメージ

この間引により出力する周波数の数を1/4から1/8程度まで減らしても音質を保つ事が出来ます。

対数による量子化

通常PCMはサンプルデータを線形的な数値で量子化を行いますが、人間は音の知覚が対数関数的である生理学的な特性を利用して線形的に量子化すると16bit程度の容量が必要なデータを音質を保ちながら符号部1bit、指数部3bitの計4bit程度のデータに圧縮する事が出来ます。これは浮動小数点数で言えば仮数部を取り除いたものと言えます。

対数による量子化のイメージ

今回の実装では対数の底は2を使用しますがデータの設計によって底は決めると良さそうです。例えば8bitでデータを保存する場合は底が2だとbitの幅に対して数値の精度が荒くなったりします。

エンコードの例

y_n = -(\log_2 \frac{|x_n|}{2^{15}})\\
s_n = sgn(x_n)
encodedFrequencies[i] = -Math.log2(Math.abs(inputFrequencies[i]) / (1 << 15));
frequencySigns[i] = inputFrequencies[i] < 0 ? -1 : 1;

デコードの例

x_n = 2^{15} \times 2^{-y_n} \times s_n
outputFrequencies[i] = (1 << 15) * -Math.pow(2, -encodedFrequencies[i]) * frequencySigns[i];

スケール値を適用

対数による量子化は音量の大きな音には良いのですが音量が小さな音に対しては音声が歪んでしまいます。
そこで各周波数の最大値をスケール値としてデータに保持します。これは浮動小数点数で言えば仮数部に相当する数値になります。

エンコードの例

y_n = -(\log_2 \frac{|x_n|}{scale})\\
s_n = sgn(x_n)
encodedFrequencies[i] = -Math.log2(Math.abs(inputFrequencies[i]) / scale);
frequencySigns[i] = inputFrequencies[i] < 0 ? -1 : 1;

デコードの例

x_n = scale \times 2^{-y_n} \times s_n
outputFrequencies[i] = scale * -Math.pow(2, -encodedFrequencies[i]) * frequencySigns[i];

サブスケール値を適用

対数による量子化により音量の大きな音、小さな音に対応出来るようになりましたが各周波数の精度は周波数全体の最大値に左右されてしまいます。つまり最大値の周波数の音質は良好なものになるのですが、それ以外の周波数帯の音質は有効な数値が小さい分、劣化します。

例えば比較的静かな状態で音量が大きな低音と音量が少し低い高音が同時になっているような場合に高音の周波数帯の音質が低下が顕著になってしまうといったものになります。

そこで各周波数でバンドを区切り、スケール値の補助的な数値を保持します。ここでは対数の数値オフセットを対数化されたサンプルデータに適用することにします。周波数バンドの区切り方は比較的重要なデータが多い低周波数は密し、高周波数に行くにつれ範囲を大きくとるようにしました。

エンコード時

y_n = -(\log_2 \frac{|x_n|}{Scale}) + sub_k\\
s_n = sgn(x_n)
encodedFrequencies[i] = -Math.log2(Math.abs(inputFrequencies[i]) / mainScale) + subScale[j];
frequencySigns[i] = inputFrequencies[i] < 0 ? -1 : 1;

デコード時

x_n = scale \times 2^{-y_n - sub_k} \times s_n
outputFrequencies[i] = mainScale* -Math.pow(2, -encodedFrequencies[i] - subScale[j]) * frequencySigns[i];

出来上がったもの

UIが貧弱ですが上記のものを全て実装したものがこちらになります。

音声圧縮用のWebアプリケーション

↓実際に動作するもの

https://redlily.github.io/training-webaudio-compression

デフォルトの設定で48000Hz、ステレオのデータを周波数、チャネル数を変えずに且つ音質もそこそこ保ったまま、同じ条件のwav形式の無圧縮データと比較して1/8程度のビットレート170kpbs程度のデータ量に圧縮する事が出来ます。

AudioContext.decodeAudioの機能でwav形式の他にMP3、Vorbis、AACとブラウザが対応していれば、どんな形式でも圧縮対象のデータとして読み込むことが可能ですが、wav形式の使用が推奨です。

感想としてはMP3等の既存の修正離散コサイン変換を利用した音声圧縮には劣るものの個人で設計し実装した割には音質を保って圧縮できるものが出来たかと思っています。

機能

  • 圧縮対象のデータ読み込み
  • 圧縮済みのデータの読み込み
  • 圧縮オプションの選択
    • ステレオ、モノラルの選択
    • サンプリング周波数の選択
    • 処理サンプル数の選択
    • 周波数帯の本数の選択
  • データの圧縮
  • 圧縮済みデータの再生

参考

成果物

デコーダ、エンコーダの実装の細かい詳細は、この記事の中で書ききれないので知りたい方は上記のソースコードを参照してくれれば有り難いです。

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

【CSS/Javascript】手書き風の文字を錬成して時代錯誤に立ち向かえ

TL;DR

  • 手書き文化は滅べ
  • それっぽく対抗してみた
  • 実運用は自己責任で…

intro

わし「ES提出したるで~、OpenES(※)でいくらでも出したるわ」
企業「手書きね^^」
わし「」

こういう非効率の塊みたいな企業が死滅することを祈りながら、技術的に解決することを試みる。

※OpenES: Web上で書いてそのまま提出できるES。ガクチカ(学生時代に力を入れていたこと)という9割の人間はろくに書けない質問などを提供してくれる

目標

ぱっとみ手書きっぽくないようなフォントの組み合わせとかを錬成。

使用フォント

1: それっぽいフォントを組み合わせる

CSSを整備する。

@font-face {
    font-family: 'Kalam';
    font-weight: bold; /* ここをboldに指定することで、標準の太さフォントを太字と誤認させる */
    src: local('Kalam'), local('Kalam-Regular'),
         url('./fonts/kalam/kalam-v9-latin-regular.woff2') format('woff2'),
         url('./fonts/kalam/kalam-v9-latin-regular.woff') format('woff');
}
@font-face {
    font-family: 'dartsfont';
    src: local('dartsfont'), 
         url('./fonts/dartsfont/dartsfont.eot?#iefix') format('eot'),
         url('./fonts/dartsfont/dartsfont.woff') format('woff');
}

/* 手書き風 */
.hw {
    font-family: 'Kalam', 'dartsfont';
    font-weight: bold;
    letter-spacing: -1px;         /* そのままだと若干幅が広い。お好みで */
    text-shadow: 0 0 1px #EEE;    /* 影をつけると文字が濃く見える。好み */
    color: #222;                  /* ここもお好み */
    transform: rotate(0.05deg);   /* Windows Chromeできれいに描画してもらうため */
}

フォントはcssの中身見て適宜配置。これでひとまずそれっぽくなる。

この他に、行間とかを調整するためにpを修飾する。

p {
    white-space: pre-line;  /* コピペできるように */
    font-size: 1.4rem;      /* 文字サイズ。任意 */
    line-height: 1.3;       /* 行間。任意 */
    line-break: strict;     /* 禁則処理。あったほうが自然 */
    text-align: justify;    /* 両端揃え。これも多分あったほうがいい */
}

これで準備1は完了。
以上のCSSを使用して、文章をHTMLで書いてみるとこんな感じで表示される。
(文章は適当にここから拝借)
image.png

わりとそれっぽい。
半角数字は全体的に詰まってみえるため、1文字なら全角を使うことを推奨。
(上の画像でいうと「学生位」の部分)

2: ランダム性を導入する

コード

このままだとPCで出力した感がまだ残っている。
というのも、各フォントごとの文字の形が全く同じだからである。

これを解消するのにJavascriptを使用する。
jQuerysugarjsを使用して、こんな感じで書いてみた。

Sugar.extend();

$(() => {
    var hws = $(".hw");
    hws.each((i,e) => {
        console.log(e.textContent.split(''));
        e.innerHTML = e.textContent.split('').map(t => {
            if(t === '\n') return `<br/>`;
            if(t === '\t') return ``;
            return `<span style="${generateRandomizeStyle()}">${t}</span>`;
        }).join("");
    });
});

function generateRandomizeStyle(){
    const CharBeautyRate = 500; // 文字の綺麗さ指数。だいたい100-1000の範囲
    var color = Number.random(0, 0x30);
    var colorRgb = [1,1,1].map(e => color);
    var tax = 1 + Number.random(-10, 10) / CharBeautyRate;
    var tby = 1 + Number.random(-10, 10) / CharBeautyRate;
    var tay = Number.random(-10, 10) / CharBeautyRate;
    var tbx = Number.random(-10, 10) / CharBeautyRate;
    var transfromLocate = [1,1].map(e => Number.random(-5, 5) / 10);
    var letterSpace = Number.random(-8, -5) / 5;
    return `
        display: inline-block;
        white-space: pre;
        color: rgb(${colorRgb});
        letter-spacing: ${letterSpace}px;
        transform: matrix(${tax},${tay},${tbx},${tby},${transfromLocate});
    `;
}

各文字ごとに<span>で囲み、それぞれについてランダムで少し文字を変形させる。
このとき、あんまり派手に変形させると変な文字になるので、微小にずらしている。
調整しているパラメーターは「色」「文字空白」「文字形状」の3つ。

文字形状の調整

色、空白はランダム化させるだけでいいが、形状はそこまで単純でもない。
今回は、transform:matrixを使用した。

matrixは行列形式で文字変形を指定する。
詳しくはググってもらうのが早いが、簡単に説明すると
matrix(a, b, c, d, e, f)と指定した場合、前半のa~dの部分が行列になる。

\begin{matrix}
a & b \\
c & d 
\end{matrix}

一切の変換を施さない行列はというと、

\begin{matrix}
1 & 0 \\
0 & 1 
\end{matrix}

になる。
今回は少しだけ動かしたいので、

\begin{matrix}
1+δw & 0+δx \\
0+δy & 1+δz 
\end{matrix}

といったようにパラメーターを微小に動かす。
ここの値の動かし度合いで字の汚さを簡易的に指定することができる。

先程のコードでいうと

var tax = 1 + Number.random(-10, 10) / CharBeautyRate;
var tby = 1 + Number.random(-10, 10) / CharBeautyRate;
var tay = Number.random(-10, 10) / CharBeautyRate;
var tbx = Number.random(-10, 10) / CharBeautyRate;

CharBeautyRateの部分である。

サンプル例

上記コードを当てはめると、こんな感じの文字になる。

CharBeautyRate=500
CharBeautyRate=500

CharBeautyRate=300
image.png

CharBeautyRate=100
image.png

CharBeautyRate=50
image.png

CharBeautyRate=50ぐらいになるとどういった変形を施しているかが明確になる。
徐々に文字が変な形になることがわかるだろう。
手書き風かつ丁寧な文章にしたいので、200-500ぐらいが無難だろうか。

今回はNumber.randomで一様乱数を使っているが、正規分布に従わせるとよりそれっぽくなりそう。

demo

つくりました。
https://arika0093.github.io/handwriting/tools.html

image.png
生成ボタンを押すたびに字の形状が毎回変わります。

課題

  • フォントの太さが均一
    • text-shadowでうまいこと調整できるか
    • ボールペン字ならそこまで気にならないか
  • とめはね等の調節
    • さすがに厳しい
  • 文字変形の単純さ
    • うまい方法があまり思いつかない
  • 字の「癖」の再現
    • transform:matrixを一様乱数でなくある程度の傾向をつける等?
  • 実際のESに貼り付ける方法
    • Word等で位置を調節して印刷が一番無難?
  • いつ使うのか
    • どうでもいい企業にしか使えないよね…

参考文献

https://xar.sh/post/95166529739/
https://sugarjs.com/docs/#/Number/random
https://qiita.com/junya/items/a1ad6126fa0315acc2aa
https://ginpen.com/2018/11/13/understanding-transform-matrix/
https://understanding-transform-matrix.ginpei.info/

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

React Static 公式ドキュメント和訳 「基礎概念」編

React Static とは

Reactベースの静的サイトジェネレータです。
つまり、全てハードコードの静的サイトはもちろん、ブログのようなCMSベースのサイトも

  • React+αの知識で!
  • DBなしに!
  • サーバーの(大した)設定もせず!
  • 超高速表示に!

作れる優れもの。
僕のようなフロントエンドに知識が偏ってる人間には特に福音だったりします。

その中でもReact Staticは、Mediumで3,000clapを叩き出した記事 「2019年のReactジェネレータ」でもGatsbyに続き2番手に選ばれたイケてる子!
しかもGatsbyよりも学習コスト≒React+αのα部分が少ないと僕の中で噂に。

ということで公式ドキュメントの「基礎概念」(Core concepts)部分を以下に訳します。

続編 React Static 設定大全 https://qiita.com/IYA_UFO/items/b01ca2eb1ec0082c4b79

MIT License
Copyright (c) 2013-present, Nozzle, Inc.
https://github.com/nozzle/react-static/blob/master/LICENSE


概要

React Staticは他の多くのReactベースの静的サイトジェネレータとは異なります。データから静的ファイル、さらにはプログレッシブに強化された(progressively enhanced)Reactアプリケーションに至るまでのとても自然なデータの流れを持っています。これにより、「データ」と「テンプレート」の関心の分離を手軽に行えます。また、データとテンプレートを可能な限りはっきり分離し、React自身がそうであるように、データの写像としてのサイトを、1つの流れの中でビルド・視覚化することができます。

開発中に起きること

  1. サイトに必要な全てのデータは、事前にstatic.config.jsに好きな方法で集められます。取得元はマークダウンファイル、ヘッドレスCMSs、GraphQLのエンドポイントからなどなど自由です。データはビルド段階でコンパイルされます。
  2. ページは1つのReactコンポーネントをexportするファイルとして定義され、必要なときにレンダリングされます。pages内のファイルは自動的にルーティングされます。
  3. ページコンポーネントを指定して、静的なルーティングを設定することも可能です。
  4. React StaticのコンポーネントにあるRoutePropsSitePropsを使ってルートごとのデータを取得しページをレンダリングすることができます。これらのコンポーネントのHOC版も利用できます。
  5. 以上を設定すれば、React Staticは正確に、スピーディーに全ページを出力します。

クライアントサイドで起きること

  1. 最初のロードでは、最速でページを表示するために最低限のアセットだけがダウンロードされます。これには、ページ特有のHTMLと、ビルド時にエクスポートされた全てのCSSが含まれます。
  2. そのページに同期的に必要なデータがHTMLから抽出されます。
  3. ReactがHTMLにアプリケーションをマウントします
  4. サイトの残りの部分は、ページ遷移が起こるたびにpreload・キャッシュされ、遷移は即座に起きているように見えます。

flow.png

コード・データの分割

React Staticはとてもユニークで素晴らしいやりかたで、各ページに必要な最小限のデータをピッタリのタイミングでリクエストします。React Staticは以下の要素に従ってコードとデータを分割します。

ページテンプレート

static.config.jsにルートを書いておくだけで、React Staticは内部的・自動的に各ルート用のテンプレートを分割します。

ページデータ

各ルート用のgetData関数の結果が、そのページ専用のJSONファイルとしてHTMLの隣にexportされます。これで9割のケースでデータをうまく分割できます。また、複数のページで何度もアクセスされるデータなどについてさらに最適化したい場合は、以下で説明するsharedDatacreateSharedDataの2つのAPIでより詳しい操作が可能です。

サイトデータ

多くのルートで必要なデータは、config.getSiteData関数に渡すことで全てのページからアクセスできるようになります。

react-universal-componentを使った手動のコード分割

React Staticはデフォルトでreact-universal-componentをサポートしています。つまり、React Staticが持つ自動コード分割に加え、必要であれば手動で巨大なコンポーネントを分割することができます。動的インポートの例を見てください。簡単です!

ページ共有データ(上級者向け)

ほとんどのプロジェクトでは必要ありませんが、稀に「全てのページで使うわけではないが、複数のページで全く同じデータを使う」場合があります。その対応には、ページ共有データAPIを使って複数ルートで同じデータを1つのJSONファイルとして共有することができます。例はこちら

ユニバーサルな、「Nodeセーフな」コードを書く

React StaticはブラウザとNode(ビルド中)の両方で動くため、コードの全てが「ユニバーサル」、言い換えれば「Nodeセーフ」であることがとても重要です。私達の多くはブラウザでJavaScriptを書くことになれているので、以下のような点に注意が必要です。

  • windowdocumentやブラウザAPIを利用する場合、利用の前に存在チェックをしてください。最も簡単な方法は、これらを利用するコードをcomponentDidMountの中に書くか、if文の中に書くことです。
if (typeof document !== 'undefined') {
  // documentオブジェクトを使う
}
  • windowdocumentやブラウザAPIに依存するライブラリがNode環境でインポートされないようにしてください。これらのライブラリの一部はブラウザのオブジェクトをすぐに必要とするので、ビルド時にエラーが出ます。エラーを解消するには、スタブを用意して条件分岐で内容をrequireしてください。
let CoolLibrary = {} // Nodeで動かす必要があるコードではこのスタブを利用する.
if (typeof document !== 'undefined') {
  CoolLibrary = require('cool-library').default
}

環境変数

色々試すなかで、特定の環境変数が必要になるかもしれません。以下がReact Static全体で利用できます。

process.env.REACT_STATIC_ENV

以下のどれかになります

  • production - webpackで本番用にビルド中
  • development - webpackで開発用にビルド中
  • node - nodeでSSR用にビルド中

本番用のビルド

本番用ビルドの前に、いくつか追加で準備することをおすすめします

  • static.config.jssiteRootを追加してください。siteRootによってReact Staticは絶対パスのリンクを最適化します。また、もしもアプリケーションがhttps://mysite.com/my-static-site/などルート以外の場所で動作する場合も、この設定により普通に機能するようになります。
  • react-static build --stagingを使って、ローカルで本番ビルドをテストしてください。このコマンドでは、本番用のビルドを行いますが、特別にlocalhostで普通に見られるようになります。
  • 本番ビルドでバグを見つけたら、ビルドコマンドに--debugをつけてコードの圧縮を停止できます。

ビルドの準備ができたら、react-static buildで本番用ビルドを開始してください。本番用のファイルはdistファイル、またはあなたがカスタム設定したフォルダに出力されます。このフォルダの中身をホストにアップロードしてください。

継続的インテグレーション

サイトが頻繁に更新される場合、何らかのサービスを使って継続的インテグレーションを設定すると良いかもしれません。よくあるのは、NetlifyとそれにリンクしたGithubリポジトリの組み合わせです。これにより、コードが変更された時に自動でサイトを再ビルドすることができます。素晴らしい!
カスタマイズ可能なホスティングサービスを探している場合、Travis CIを使ってカスタマイズされた場所にデプロイするのも良いでしょう。可能性は無限大!

ホスティング

過去、静的サイトのデプロイがこんなに簡単だった時代はありません。静的サイトを安く、または無料でホストできるサービスがたくさんあります。実際これは静的サイトの最大のメリットの1つでもあります。つまり、サーバーのメンテナンスが不要で、スケーラビリティをあまり心配しなくて良いのです。以下はおすすめのサービスです。

CMSを使う

CMSはサイトの整理や更新にとても便利です。React Staticチームのお気に入はGraphCMSContentful
Netlify CMSですが、 https://headlesscms.org/ (React Staticで作られています:wink:)で自分に合うものを選んでも良いでしょう。

Webhookで再ビルドする

CMSをつかう場合、CMSが変わったときサイトをビルドし直したいな、と考えると思います。
Webhookを使いましょう!ほとんどのモダンなCMSはWebhookを提供します。これらは、単純にCMSに変更があったときにpingされるURLです。CIツールやホスティングサービスに自動リビルドさせるのに使うのが効果的です。

例:

404エラーのハンドリング

React Staticで404ページを作るのは簡単です。サーバーによっていくつかの異なる方法で設定できます。

  • 404.js Reactコンポーネントをpagesに置く
  • 以下のルートを設定する
{
  path: '404',
  component: 'path/to/your/404/component.js'
}

404コンポーネントはどう使われるか

  • 404 コンポーネントはビルド時にルートの404.htmlファイルとして出力されます。多くのサーバーはルートが存在しない時に自動的にこのファイルを利用します。
  • もし<Routes />コンポーネントがレンダリングされて、マッチするルートやテンプレートがない場合、404コンポーネントが表示されます。

動的ルーティング

訳者がよくわからないのでスキップ
https://github.com/nozzle/react-static/blob/master/docs/guides/dynamic-routes-reach-router.md

Webpackのカスタム設定とプラグイン

React StaticはReact用に調整された素晴らしいデフォルトのWebpack configを持っています。これだけでほとんどの場合は十分なはずです。しかし、設定を変えたくなったらnode.api.jsファイルを設置してwebpack APIで拡張することができます。

ページネーション

ページネーションガイドをお読みください!

ブラウザサポート

React StaticはReact自体のサポートブラウザとあなたが選ぶBabelのpolyfillに依存してサポートの範囲を決めます。

  • モダンブラウザ(Chrome, Firefox, Safari)の最新版はデフォルトでサポートされています。
  • IEはサポートできますが、babel-polyfillをが必要です。

static.config.jsを拡張してIEに対応させるには、まずはbabel-polyfillをインストールしてください。

その後、以下のオブジェクトをstatic.config.jsのエクスポートに追加して、既存のwebpack設定を拡張してください。

webpack: (config, { stage }) => {
  if (stage === 'prod') {
    config.entry = ['babel-polyfill', config.entry]
  } else if (stage === 'dev') {
    config.entry = ['babel-polyfill', ...config.entry]
  }
  return config
},
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

文字列の中にある全ての並んだパターンを作る

タイトルって難しいな...

やりたいこと

abcabc

の中から

abcabc, abcab, bcabc, abca, bcab, cabc,...

といった感じにパターンを作りたい。

結論

var str = "abc".repeat(2);
var arr = []
for (var i = 0; i < str.length; i++) {
    for (var c = 0; c <= str.length - (str.length - i); c++) {
        if (str.length - i <= 1) {
            continue;
        }
        arr.push(str.substr(c, str.length - i));
    }
}

3行目の

for (var i = 0; i < str.length; i++) {

のiの初期値を1にすると最初のabcabcというただの元の文字列が除外できる。

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

【JavaScript】if命令による条件分岐

if命令でできること

 if命令によって、「もし〇〇ならば✕✕を行う」というように、一定の条件を満たすか否かに応じて実行する処理を変える(分岐させる)ことができます。

条件を満たす場合の処理を指定する - if -

 if命令の中に与えられた条件式の真偽値がtrue(真)の場合、指定した処理が実行されます。

See the Pen if-1 by OSHMVTRV (@oshmvtrv) on CodePen.

 変数iは14であり、条件式「i >= 10」を満たす(真偽値がtrueである)ため、処理が実行され「iは10以上です」と表示されています。仮に変数iが10未満の場合、条件式を満たさないため処理は実行されず何も表示されません。

条件を満たさない場合の処理を指定する - else -

 条件を満たす場合だけでなく、満たさない(真偽値がfalseである)場合の処理も指定したい場合は、elseを使うことで処理を実行できます。

See the Pen else-1 by OSHMVTRV (@oshmvtrv) on CodePen.

 変数iは8であり、条件式「i >= 10」を満たさないため、「iは10以上です」は表示されません。しかしelseを使って指定した処理は実行され「iは10未満です」と表示されています。

分岐を追加する - else if -

 ifとelseのみでは2つしか処理を分岐させるとこができませんが、else ifを使って分岐を追加することができます。

See the Pen else if-1 by OSHMVTRV (@oshmvtrv) on CodePen.

 変数iは8であり、条件式「i >= 10」を満たさないため、「iは10以上です」は表示されません。しかしelse ifを使った条件式「i >= 5」は満たすため、指定した処理は実行され「iは5以上です」と表示されています。

※処理が実行される順番

 複数の条件式を満たす場合、実行されるのは一番最初に条件を満たした処理のみです。if命令は記述する条件式の順番にも注意が必要です。

See the Pen else if-2 by OSHMVTRV (@oshmvtrv) on CodePen.

 複数の条件式のうち、2番目の「i >= 3」と3番目の「i >= 5」を満たしますが、記述上最初に「i >= 3」を満たすため「iは3以上です」と表示されています。

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

$.ajaxでクロスドメイン対応(IE9)

アプリが動作する別のサーバにリクエストを投げた際、ChromeやIE11などのブラウザでは問題なく動作したのですが、IE9で上手くいかなかったことがありました。その際の対応を記しております。

CORS

Cross-Origin Resource Sharingの略。通常XMLHttpRequestは Same-Origin Policyに従います。
ですので、http://hoge-a.com で提供されているアプリケーションのフロントエンドJavaScriptから、XMLHttpRequestで http://hoge-b.com/add へのリクエストは別オリジンへのアクセスとなり、制限されます。
オリジン間でこのようなリクエストを許可したい場合、サーバ側のレスポンスヘッダに Access-Control-Allow-Origin で許可するオリジンを指定します。
参考:オリジン間リソース共有 (CORS)

IE9で上手くいかない

今回直面したケースは、フロントエンドJavaScriptから $.ajax を使って別オリジンに対してXMLHttpRequestを投げるというものでした。サーバ側にはAccess-Control-Allow-Origin を指定して、Chrome, IE11でレスポンスが帰ってくることを確かめました。しかしIE9で実施するとレスポンスが返ってこない...

対応

jQuery.support.cors = true;

ブラウザがXMLHttpRequestを生成でき、そのオブジェクトがwithCredentialsプロパティを持っていればtrueです。 corsがサポートされていない環境で、クロスドメインXHRリクエストを許可し、クロスドメインリクエストを実行するには $.support.cors = true;を行なってください。

参考:$.support | jQuery 1.9 日本語リファレンス | js STUDIO

CORSがサポートされていないブラウザで別オリジンへのXMLHttpRequestを実行する際は、コード内で上記を含める必要があります。対応後、IE9でもレスポンスが返ってきました!

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

文字が要素の配列で重複していない文字だけ取り除く

結論

("js".repeat(5)+"bavcab").split("").filter(function (e, i, self) {return self.filter(function (_e) {return _e===e}).length>=2})

この場合はvcが配列から消える

参考

https://qiita.com/cocottejs/items/7afe6d5f27ee7c36c61f

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

TypeScript と webpack で作る npm パッケージ

はじめに

TypeScriptwebpack を使って npm パッケージを作成して publish する方法を紹介します。実際に npm パッケージを publish する際に考慮しておいたほうがよい TypeScript や webapck の設定については省略しているので悪しからず。

環境

  • Node.js 10.13.0
  • npm 6.4.1
  • TypeScript: 3.3.4000
  • ts-loader 5.3.3
  • webpack 4.29.6

package.json ファイルの作成

以下のコマンドで package.json ファイルを作成します。

npm init

次の項目の入力を求められますが、すべて後で変更可能なので気軽に決めて OK です。 ただしパッケージ名は重複不可なので、既に同じ名前のものが存在しないか確認しておいた方がよいでしょう。

  • package name
  • version
  • description
  • entry point
  • test command
  • git repository
  • keywords
  • license

パッケージのディレクトリ構成

特に決まりはありませんが、ここでは例として次のような構成で説明します。

.
├── dist // ビルド後の成果物の出力先
├── package.json
└── src // ソースコード

webpack を使ったビルド処理

まず webpackwebpack-cli をインストールします。

npm install --save-dev webpack webpack-cli

次に webpack の設定ファイルを作ります。

モジュールの定義方法によって設定内容は異なりますが、今回は UMD で作ることにします。 library に指定した名前はブラウザ上で実行する際のグローバル変数名となります。ここでは Calc としましたが任意です。なお globalObject については注釈を見てください。1

webpack.config.js
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'index.js',
    path: path.resolve(__dirname, 'dist'),
    library: 'Calc',
    libraryTarget: 'umd',
    globalObject: 'typeof self !== \'undefined\' ? self : this'
  }
};

次に src/index.js を用意します。ここでは例として次のような内容にします。

src/index.js
export function sum(a, b) {
  return a + b;
}

ビルド処理を package.json に記述します。

diff --git a/package.json b/package.json
index 77904e2..9810380 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,8 @@
   "description": "",
   "main": "index.js",
   "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "build": "npx webpack --config webpack.config.js"
   },
   "repository": {
     "type": "git",

試しにビルドしてみましょう。正常にビルドできれば次のような表示がされるはずです。

npm run build

...

> npx webpack --config webpack.config.js

Hash: e08c8cfaa6ee7d9b3aae
Version: webpack 4.29.6
Time: 94ms
Built at: 2019-03-23 08:57:52
   Asset       Size  Chunks             Chunk Names
index.js  956 bytes       0  [emitted]  main
Entrypoint main = index.js
[0] ./src/index.js 28 bytes {0} [built]

ソースコードを TypeScript で書けるようにする

TypeScriptts-loader をインストールします。

npm install --save-dev typescript ts-loader

次に tsconfig.json ファイルを用意します。ここで設定内容を変更しても構いませんが、ひとまずはデフォルト設定のままでも OK です。

npx tsc --init

次に webpack の設定を修正します。

diff --git a/webpack.config.js b/webpack.config.js
index 8cd9058..9d35ec9 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -2,7 +2,15 @@ const path = require('path');

 module.exports = {
   mode: 'development',
-  entry: './src/index.js',
+  entry: './src/index.ts',
+  resolve: {
+    extensions: [".ts", ".js"],
+  },
+  module: {
+    rules: [
+      {test: /\.ts$/, loader: 'ts-loader'},
+    ],
+  },
   output: {
     filename: 'index.js',
     path: path.resolve(__dirname, 'dist')

entry に指定するファイルの拡張子を変更しているので、ファイルもリネームしておきます。

mv src/index.js src/index.ts

これでソースコードを TypeScript で書くことができるようになっているはずです。試しに src/index.ts を次のように書き換えてみます。

src/index.ts
export function sum(a: number, b: number) {
  return a + b;
}

再度ビルドして出力されたファイルを確認すると src/index.ts に書いた内容が出力されていることがわかります。

npm run build
tail -n 10 dist/index.js
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

"use strict";
eval("\nObject.defineProperty(exports, \"__esModule\", { value: true });\nfunction sum(a, b) {\n    return a + b;\n}\nexports.sum = sum;\n\n\n//# sourceURL=webpack://Calc/./src/index.ts?");

/***/ })

/******/ });
});

npm パッケージのエントリーポイントとなるファイルを更新する

ここまでで dist/index.js にビルドされた JavaScript が出力されるようになりました。このファイルが npm パッケージのエントリポイントとなるように package.json を修正します。

diff --git a/package.json b/package.json
index 1b982a9..593a3d5 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "version": "1.0.0",
   "description": "",
-  "main": "index.js",
+  "main": "dist/index.js",
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",
     "build": "npx webpack --config webpack.config.js",

パッケージの公開前のビルド処理を定義

用意したビルド処理を npm パッケージとして publish する前に行う処理として定義します。 npm-scripts の prepare として定義することで publish する前にビルド処理を行い、生成された成果物をパッケージに含めることができます。

diff --git a/package.json b/package.json
index 1a5ce7e..1b982a9 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,8 @@
   "main": "index.js",
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",
-    "build": "npx webpack --config webpack.config.js"
+    "build": "npx webpack --config webpack.config.js",
+    "prepare": "npm run build"
   },
   "repository": {
     "type": "git",

パッケージの公開

まだ npm のアカウントを持っていなければココから作ります。

作成したアカウントでログインします。

npm login

あとは以下のコマンドでパッケージを公開することができます。テストや Linter などを用意している場合は事前に実行するなどして正常に動作することを確認しましょう。

npm publish

バージョンの更新

Semantic Versioning に従って更新するのが一般的です。

  • API に後方互換性のない変更がある場合にはメジャーバーションを更新
  • 後方互換性のある機能追加の場合にはマイナーバーションを更新
  • 後方互換性のあるバグ修正の場合にはパッチバーションを更新

npm のサブコマンドとしてそれぞれ以下のコマンドが用意されているので、変更内容に合うものを使います。

npm version major
npm version minor
npm version patch

実行すると package.json, package-lock.json 内に記述された version プロパティが更新されます。また Git リポジトリであるならばバージョンと同名のタグが作成されます。

パッケージの公開時と同様に、再度 npm publish することでバージョンを更新することができます。

npm publish

  1. 2019-03-23 現在、 UMD で出力する際に起きる問題の回避措置です。将来的には必要なくなっている可能性があります。 

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

【jQuery 】イベント、フォームに入力した文字を数える、$(this)の用法

TECH::EXPERTの応用カリキュラムを進めていて最初につまづいたポイントです。

「問題はHTMLファイルに適当なフォームを追加して入力した文字数を表示しましょう!!」というもの

この記事ではjQueryでの記述で
イベントがなんなのか
$(this)が何を示しているのか
を理解できると思います。

書いたコードはこちら

sample.html
    <form>
      フォーム:<input type="text" name="name" id="input-text">
    </form>

    <div id="char-count">
      0文字
    </div>
sample.js
$(function() {
  $("#input-text").on("keyup", function(){
    var charNum = String($(this).val().length);
    $("#char-count").text(charNum + "文字");
  });
});

解説:

$("#input-text").on("keyup", function(){ 処理 });

ここではHTMLファイル内のid="input_text"に対して.onメソッドで"keyup"というイベントが発生した時にする処理を記述しています。

ここでのfunction()は無名関数と呼ばれ、1回しか呼び出す必要がないため特に関数名を指定していません。

関数の中

String($(this).val().length);
メソッド 処理
String() 処理した後、文字列を返す
$(this) $("#input-text")と同値、イベント発火元の要素
.val() メソッドを適用している要素のバリューを返す
.length 要素の数を返す

input_text要素のバリューの数を文字列として返しています。

例えば、フォームに「おはよう」と記述すれば変数charNumには「4」が代入される訳です。

最後に

$("#char-count").text(charNum + "文字");
メソッド 処理
.text 要素内のテキストを置き換える

イベントが発生するたびに、フォームの下に「〇〇文字」と表示しています。

以上で処理の解説は終了です!!

ここまで読んでいただきありがとうございました。
こういった基本動作をたくさん使って動的なページを作れるようにこれからも学習を続けて行きます。

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

Flowtypeのバージョンアップを難なくこなすために型アノテーションを理解する(React Hooksの導入にも)

React + Redux + React Routerを導入したSPAアプリケーションで、Flowのバージョンを上げるのに少し苦労したのでその知見を共有します。

Flowバージョンが0.84以下でReduxを採用しており、Flowのバージョンアップをした時にほぼ必ず起こるエラーですが、意外と英語情報を含めてまとまった解説がなかったのでお役に立てればと思います。

Flow 0.85からexport時にアノテーションが必須となった

React Hookを使うべくReactとFlowのバージョンを上げ、意気揚々と動作確認をしようとしていたところ、Redux周りで大量のFlowエラーが検出される…なんてことがこれから起こるかもしれません。

既存プロジェクトでFlowのバージョンが0.84以下を採用している場合、React Hooksを使うにはFlowのバージョンを0.87以上に上げる必要があります。

しかしFlowの0.85から既存コードにエラーが検出されうる変更が加えられているので、その内容を抑えておく必要があります。

詳しい変更内容は以下のリンクにあるのですが、英語情報なので要約しながら解説していきます。
- Asking for required annotations

既存コードにエラーが発生するのに主に「ジェネリック型にアノテーションを付与しないままexportした時」です。「何を言っているのか分からない」という方もいるかもしれませんが、これさえ理解できれば全て解決するので少しだけ頑張って読み進めてください。

そもそもジェネリックとは型を動的に設定するもので、Javaではおなじみですが、FlowやTypeScriptでも重宝されています。例えば以下のようなクラスがあったとします。

class Demo<T> {
  x: T | null = null;
  get(): T {
    if (this.x == null) {
      throw new Error("unset");
    }
    return this.x;
  }
  set(x: T) {
    this.x = x;
  }
}

この時Tという型が一意に存在するのではなく、string型でもnumber型でも一貫した値を使っていれば、flowが適切に型チェックを行なってくれます。

const demo = new Demo();
demo.set('hoge');
const val1 = demo.get(); // setでstring型を指定しているので、getは自動的にstring型になる

しかしこのままインスタンスをexportするとflowエラーが出てしまいます。

const demo = new Demo();
demo.set('hoge');
export { demo };
14: const demo = new Demo();
                 ^ Missing type annotation for `T`. `T` is a type parameter declared in `Demo` [1] and was implicitly instantiated at new `Demo` [2].
References:
3: class Demo<T> {
         ^ [1]
14: const demo = new Demo();
                 ^ [2]

なぜexportした途端にエラーが出てしまうのでしょうか。

型アノテーションをつける理由とは

先程掲載した記事には、アノテーションが要求される要因として「型を正確に推測すること」、そして何より「パフォーマンスの大幅な向上」が挙げられています。

ジェネリックの型が確定しないままexportされると、その依存元に至るまでを全ての箇所を調べなくてはならず、「理論的には可能だが、実際のコードベースのサイズまでスケールさせるのは不可能」と説明されています。

ちなみに「アノテーションを付与」とは以下のことを指します。

const demo = new Demo<string>();
demo.set('hoge');
export { demo };

new Demoとなった時点でconst demoの型が確定するので、exportしても上記の問題は発生しません。

こうした明示的なアノテーションによって並列処理が可能となり、大幅にパフォーマンスが向上するほか、型を正確に推測するカバレッジが上がることが期待されています。

要するにFlow設計者側の要求ではあるのですが、いずれにせよReactの新機能を使うなどの際にはこれにキャッチアップする必要があります。

Redux, React-Reduxの型アノテーションに対応する

ジェネリックを理解しないままバージョンを上げると真っ先にハマるのはReact, React-Redux周りではないでしょうか。

利用者が多い割に情報が少なく、GithubのIssueを見ても以下のような付け焼き刃の対応が紹介されていたりします。

const container = connect<*, *, *, *, *>(
  mapStateToProps,
  mapDispatchToProps
)(Component);

(*は型推論を強制するものですが、ほとんど型定義を放棄しているので、これを行うのならFlowTypedを置く意義を考え直したほうがよいかもしれません)

React-Reduxの型定義ファイルはかなり力技にも見えますが、しっかりと型情報を失わない形でPropsをコンポーネントのPropsを生成できるように上手く設計されています。

こんな感じの型定義がズラッと並んでいて最初は圧倒されますが、実は一番の近道はこの定義ファイルをしっかりと読み解くことかもしれません。

declare export function connect<-P, -OP, -SP, -DP, S, D>(
  mapStateToProps: MapStateToProps<S, OP, SP>,
  mapDispatchToProps: DP,
  mergeProps: MergeProps<P, OP, SP, $ObjMap<DP, Bind<D>>>,
  options?: ?Options<S, OP, SP, P>,
): Connector<P, OP, P>;

そもそものReact-Reduxの役割を考えればさほど難しくありません。

connectで行っているのは、ReduxのStateからコンポーネントに渡すための情報を抽出するmapStateToProps、同じくDispatchからコンポーネントへ渡すためのmapDispatchToPropsが主たる要素で、これにOwnPropsを合わせた3つがコンポーネントが受け取るProps型になれば良いのです。

例として以下のケースを考えてみましょう。

type StateProps = {| count: number |};
type DispatchProps = {| action: typeof Action |};
type OwnProps = {| children: React.Node |}
type Props = {| ...StateProps, ...DispatchProps, ...OwnProps |}

const Component = (props: Props) => 
  <Custom {...props} />

const mapStateToProps = (state: State): StateProps => ...
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ...

const container = connect<Props, OwnProps, StateProps, DispatchProps, State, Dispatch>(
  mapStateToProps,
  mapDispatchToProps
)(Component);

このようにconnectにアノテーションを付与すると、Flow側でもOwnProps、StateProps、DispatchPropsの3つの型を統合したものがPropsになるかを検証してくれます。

また多くの場合はOwnPropsを利用しませんが、その場合はvoidを指定すれば、StatePropsとDispatchPropsの2つを重ねたらPropsになるかを見てくれます。

つまりそれぞれの型を適切に定義すればFlowが通るようになっているので、エラーを地道に潰していけば自ずと良い感じに型が付いているはずです。

最後に

JavaScriptはエコシステムの進化が早すぎると言われていますが、Flowのこの変更が加わったのは2018年10月のことで、まだまだ網羅的な情報は少ないです。

一方でReact Hooksは良い意味でこれまでのReactの実装を大きく変えるものということで、早い段階でキャッチアップしたいところです。Reduxを入れているとここで紹介したエラーに遭遇する可能性が高いということで、本記事が参考になれば幸いです。

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

登録しているブックマークレット メモ

あんまりないですが、環境移行用にメモ

見ているウェブページのマークダウン形式のリンクを作る
javascript:(function(){const%20e=document.createElement('input');e.value=`[${document.title}](${location.href})`;document.querySelector('body').append(e);e.select();document.execCommand('copy');e.remove();})();
選択しているテキストを翻訳する(英→日)
javascript:(function(){const%20t=(window.getSelection%20?%20window.getSelection():%20document.getSelection%20?%20document.getSelection():%20document.selection.createRange().text);if(t)open('http://translate.google.com/translate_t?hl=ja&sl=en&tl=ja&q='+encodeURIComponent(t)+'','_blank');})()
選択しているテキストを翻訳する(日→英)
javascript:(function(){const%20t=(window.getSelection%20?%20window.getSelection():%20document.getSelection%20?%20document.getSelection():%20document.selection.createRange().text);if(t)open('http://translate.google.com/translate_t?hl=ja&sl=ja&tl=en&q='+encodeURIComponent(t)+'','_blank');})()
見ているウェブページをツイートする
javascript:(function(){window.open('https://twitter.com/share?url='+encodeURIComponent(window.location.href)+'&text='+encodeURIComponent(document.title),null,'width=500,height=400,toolbar=no,menubar=no,scrollbars=no')})()
PictureInPictureで動画を開く(Safari限定)
javascript:(function(){document.querySelector('video').webkitSetPresentationMode('picture-in-picture')})()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascript 画像を動的に切り替える

画像を動的に切り替えたい時がある。
例えばこちらのサイトのように。
https://www.street-academy.com/myclass/14616?sessiondetailid=506634&trigger=browse-history_top
つまり、サムネイル画像?をクリックするとその拡大画像が表示されるというもの

参考にした記事はこちらである。
https://www.sejuku.net/blog/63834

上記の記事を参考にして
作成したテンプレートはこちら。
基本は一つ目のサイトと同じ機能

pic_change.html
<html>
    <body>
        <img id="mypic" src="pics/1.jpg" width="400" height="300">

        <img class = "pic" onclick="slideshow('0')" src="pics/1.jpg" width="10%">
        <img class = "pic" onclick="slideshow('1')" src="pics/2.jpg" width="10%">
        <img class = "pic" onclick="slideshow('2')" src="pics/3.jpg" width="10%">

            <script>
            var pics_src = new Array("pics/1.jpg","pics/2.jpg","pics/3.jpg");

            function slideshow(num){
                document.getElementById("mypic").src=pics_src[num];
            }
            </script>
    </body>
</html>

<style>
    body .pic:hover{
        opacity: 0.6; 
        filter: brightness(110%);/*リンクにマウスが乗ったら背景色を変更する*/
        background-color:brown;
    }
</style>


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