20191128のJavaScriptに関する記事は25件です。

React初心者だけどUbuntu18.04上でcreate-react-appとReact Hooksを使ってサクッとカウンタを作る

初投稿です。私もReactの勉強を最近始めたばかりなので備忘録的に。
色々すっ飛ばしてます。

まずはUbuntuのバージョン確認

Ubuntu上で作ります。

% cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.3 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.3 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

yarnをインストール

yarn公式に従ってください
https://yarnpkg.com/lang/en/docs/install/#debian-stable

$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -  
$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
$ sudo apt update && sudo apt install yarn

create-react-appをインストール

これもyarn公式に従ってください
https://yarnpkg.com/lang/en/docs/cli/create/

$ yarn global add create-react-app

こんな感じのメッセージが出ればOK(多分)。

success Installed "create-react-app@3.2.0" with binaries:
      - create-react-app
Done in 9.51s.

$ yarn --version
1.17.3

create-react-appコマンドを叩く。

下のコマンドでカレントディレクトリにmy-react-appというディレクトリが作られる。
my-react-appの直下にReactでアプリを作るのに必要な諸々のファイルが自動的に作られる。

$ create-react-app my-react-app

成功するとこんなメッセージが出る。

Success! Created my-react-app at /home/koralle/learn/my-react-app
Inside that directory, you can run several commands:

  yarn start
    Starts the development server.

  yarn build
    Bundles the app into static files for production.

  yarn test
    Starts the test runner.

  yarn eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

  cd my-react-app
  yarn start

Happy hacking!

$ create-react-app --version
3.1.2

試しにアプリを起動してみる。

$ cd my-react-app
$ yarn start

ちょっと待つとこんなメッセージになる。

Compiled successfully!

You can now view my-react-app in the browser.

  Local:            http://localhost:3000/
  On Your Network:  http://10.0.2.15:3000/

Note that the development build is not optimized.
To create a production build, use yarn build.

言われたとおりにブラウザで http://localhost:3000/ にアクセスします。
というか人によっては自動的にブラウザでページが立ち上がります。

こんな感じ。
無題.png

次からが本題。

カウンタを作るための準備

まずはmy-react-app直下のディレクトリ構成を確認
今回触るのは./public/index.html./src/App.jsだけです。

$ cd my-react-app
$ ls
README.md  node_modules  package.json  public  src  yarn.lock

$ tree ./public
./public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt

0 directories, 6 files

$ tree ./src
./src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
└── serviceWorker.js

0 directories, 7 files

先にindex.htmlを開く。今回はできる限り余計なものを全部消します。邪魔なので。
私はこれだけ残しました。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

次は./src/App.jsを編集。
function App()全体がいわゆるAppコンポーネントっていうやつですが、
一回こいつを全部消します。
そしてAppコンポーネントをアロー関数式で書き直す。アロー関数式、おしゃれな感じが好きです。

import React from 'react';
import logo from './logo.svg';
import './App.css';

const App = () => {

} 

export default App;

一回途中経過を見たいので先にカウンタのボタンを作ります。
Appコンポーネント以外はいったん省略します。

const App = ()=> {

  return(
    <div>
      <button> + </button>
      <button> - </button>
    </div>
  );
}

こんな感じ。小さいけど確かに+ボタンと-ボタンが表示されてます。
無題1.png

次にカウンタを作ります。そのために必要なのはあと最低限以下の二つです。

  • カウンタの値を保持する変数 (*1)
  • カウンタの値を増減させる関数 (*2)

これらを実装するために、今回はReact HooksというReact公式APIの中のuseState()という関数を利用します。
詳しくは公式ドキュメントを見てください。

フック早わかり – React
https://ja.reactjs.org/docs/hooks-overview.html

書式は今回の場合こんな感じ。
countが上記の(*1)で、setCountは(*2)とイコールではないですが、(*2)の実装に必要になります。
useState(0)の0はcountの初期値です。

const [count, setCount] = useState(0);

これを使って./src/App.jsにカウンタの動作を書きますが、1行目もちょっと編集します。

import React, {useState} from 'react';
import logo from './logo.svg';
import './App.css';

const App = () => {
  // (*1), (*2)の実装に必要
  const [count, setCount] = useState(0);

  const increment = () => setCount(count+1); // カウンタの値を増やす
  const decrement = () => setCount(count-1); // カウンタの値を減らす

  // index.htmlの<div id="root"></div>の中に表示
  return(
    <div>
      <p> {count} </p>
      <button onClick={increment}> + </button>
      <button onClick={decrement}> - </button>
    </div>
  );
} 

export default App;

これで完成

カウンタの値を増やす
増やす.png

カウンタの値を減らす
減らす.png

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

三項演算子とは-基礎+応用(変数を活用した記法)

三項演算子について、学習したので、備忘録もかねてまとめます。
今回は基本となる記法だけでなく、変数を活用した記法も以下に記載します。
(変数を活用した記法は自分が実装した時に非常に苦戦しました。今回の記事を書くきっかけです。。)

三項演算子とは

if文を簡易的に記述するときに使用します。
三項演算子は3つの項目に分かれており、これが三項演算子における三項の意味する部分となってます。
その三項とは条件と2つの可能性のことを指しており、これを使用することでIf文を簡易化します。
それでは早速使い方を記載していきます!

使い方

まず、三項演算子の記載法について、以下に記載いたします。

三項演算子の書き方
条件式 ? trueの時の値 : falseの時の値

続いて具体例を用いて、if文から三項演算子に変換してみます。

if文
if apple_stock > 1
  :eat_apple
else
  :buy_apple
end

上記例の場合、三項は以下のようになります。

  • 条件式 if apple_stock
  • trueの時の値 :eat_apple
  • falseの時の値 :buy_apple

上記を踏まえて三項演算子を利用します。

三項演算子
条件式 ? trueの時の値 : falseの時の値
apple_stock >1 ? :eat_apple : :buy_apple

以上がif文を三項演算子で記載するときの基本的な方法となります。
三項演算子は上記のように条件・true・falseが一つずつの時に利用します。
複数の要素がある場合は、if文をそのまま利用しましょう。

以降では、当方が苦労した変数を利用した三項演算子について記載します。

変数を使用した三項演算子の活用法

以下は当時自分がコードを書いていた内容を例としてます。
画像がある場合とない場合で条件分岐させ、それぞれの条件にあうよう、テンプレートリテラルを使ってコードを書いていました。
最初に自分が作ったのが以下。大分冗長的ですね。。。

function buildHTML(message){
    if (message.image) {
      let html =`<div class="message">
                <div class="upper-message">
                <div class="upper-message__user-name">
                ${message.name}
                </div>
                <div class="upper-message__date">
                ${message.created_at}
                </div>
                </div>
                <div class="lower-message">
                <p class="lower-message__content">
                ${message.content}
                </p>
                <img class="lower-message__image" src=${message.image}>
                </div>
                </div>`;
      return html
    } else {
      var html = `<div class="message">
                <div class="upper-message">
                <div class="upper-message__user-name">
                ${message.name}
                </div>
                <div class="upper-message__date">
                ${message.created_at}
                </div>
                </div>
                <div class="lower-message">
                <p class="lower-message__content">
                ${message.content}
                </p>
                </div>
                </div>`;
    }
    return html
  }

上記を変数を使用した三項演算子で記載をすると以下のようになります。
考え方としてはimageという変数を三項演算子で定義します。

変数:image
条件:message.image
true: `<img class="lower-message__image" src=${message.image}>`
false: ``<=空のvalを返すことでimageがない時は画面に何も反映しないようにします。

let image = message.image ? `<img class="lower-message__image" src=${message.image}>` : ``

それでは、上記で定義した変数を組み込んでみましょう。

  function buildHTML(message){
    let image = message.image ? `<img class="lower-message__image" src=${message.image}>` : ``

    let html = `<div class="message">
                <div class="upper-message">
                <div class="upper-message__user-name">
                ${message.name}
                </div>
                <div class="upper-message__date">
                ${message.created_at}
                </div>
                </div>
                <div class="lower-message">
                <p class="lower-message__content">
                ${message.content}
                </p>
                ${image}
                </div>
                </div>`;
      return html
    }

大分、すっきり書けました!

今回、自分のやり方では上記方法でリファクタリングしましたが、より良い方法などございましたら、
ご教示いただけますと幸いです!

参考

https://www.rubyguides.com/2019/10/ruby-ternary-operator/

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

【ライフゲーム】lifegame-coreの紹介

はじめに

この記事はGlobal Day of Coderetreat 2019 in Shizuokaに参加し、ライフゲームライブラリを作るに至るまでの話です。

Global Day of Coderetreat 2019 in Shizuoka

今年(2019年)の11/9にGlobal Day of Coderetreat 2019 in Shizuokaというイベントに参加してきました。
イベントの内容をざっと箇条書すると以下のとおりです。

  • 数十分でペアプログラミングをし、お題に沿った作品を開発する
  • 今年のお題は「ライフゲーム」

参加した感想としては、コミュニケーションを取りながらプログラミングすることが楽しいと思いました。
こういうハッカソンっぽいイベントはもっと欲しいと思いました。

このイベントをきっかけに「ライフゲームのライブラリを作ってみたいな」と思ったのでJavaScript/TypeScript向けにライフゲームのライブラリを作りました。

lifegame-core

リポジトリ:hota1024/lifegame-core
npm:lifegame-core
使用言語:TypeScript
実装の話→https://speakerdeck.com/hota1024/typescriptderaihugemuwozuo-tutemita

デモ

game.ts
import { LifeGame, ArrayWorld, BasicEnvironment, Cell, ConsoleWorldRenderer, CellState } from 'lifegame-core'

const game = new LifeGame(new ArrayWorld(20, 20, Cell), new BasicEnvironment()) // Create 20 x 20 world.
const renderer = new ConsoleWorldRenderer() // Create console world renderer.

// Random generate.
game.setEach(() => {
  return Math.random() > 0.5 ? CellState.Dead : CellState.Living
})

// Tick function.
const tick = () => {
  console.clear() // Clear console.
  renderer.draw(game.world) // Draw.
  game.forward() // Forward.

  setTimeout(tick, 500)
}

// Start
tick()

最後に

こういうイベントに行くと何かを作りたくなるのでどんどんイベントに参加していきたいと思います。

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

javascript 基本その1

今日は忘れてきた、javascriptについて書きます。

JavaScriptとは

ウェブサイトで操作をして表示を変化させたい時に使用する言語です。

例1、ある箇所にマウスを乗せると、プルダウンが展開
  (カテゴリにカーソルを載せたらカテゴリ一覧が出てくる)

例2,facebookのいいねボタン

といった風に、カーソルやクリック、時間経過で変わる動的な挙動を扱います。

javaとは違う

Javaという有名なプログラミング言語があります。JavaとJavaScriptは全くの別言語です。

JavaとJavaScriptではメインで使用される分野が異なります。

Javaは業務システムなどのサーバーサイド側で使用されます。
つまり、データの保管や複雑な処理を行う「裏側」がメインです。
Rubyもサーバーサイドに分類されます。

一方、JavaScriptは前述の通り、主にフロントエンド、つまり「見た目」の部分で
活躍することが多いプログラミング言語です。

ECMA

JavaScriptが開発されてからしばらくは、異なるブラウザで互換性が無いことが問題となっていました。
こでECMAインターナショナルという機関のもとで標準化がなされます。

すなわち、ECMAインターナショナルによって「JavaScriptの書き方」が決められました。
これをECMAScriptと呼びます。現在はJavaScriptというとECMAScriptで書かれることが一般的です。

JavaScript基本文法

HTML内の場合

scriptタグによってjavascriptを適用させてことができます。

<body>
    <script>
        // この中にJavaScriptのコードを記述する
    </script>
</body>

またjsファイル(javascript用のファイル)を読み込む場合は下記のように記述します。

html
<body>
    <script src="script.js"></script> <!--これで同じフォルダ内のscript.jsが読み込まれます。-->
</body>

基本コード

window.alert()

ブラウザでアラートを表示させるメソッドです。

引数としてアラートに表示させる情報を渡します。
文字列を直接引数にしましたが、変数でも可能です。

script.js
window.alert('こんにちは');

console.log()

ブラウザのコンソールにテキストを表示させるメソッドです。

引数としてコンソールに表示させる情報を渡します。
window.alertと同様に変数を引数に渡すこともできます。

Rubyにおけるputsメソッドと同様に使えます。

script.js
console.log('こんばんは');

変数の宣言

変数を宣言する際は、varのあとに半角スペースを挟んで変数名を記述します。

script.js
var name = 'yamada'; //varで変数宣言します。
console.log(name + 'さん、こんばんは');

ES2015(ES6)バージョン以降の変数宣言の書き方

varはES2015(ES6)バージョン以前の古い書き方でもあります。

ES2015(ES6)バージョン以降は、letとconstを使います。

script.js
let name = 'yamada'; // letは、後で書き換えることができる変数宣言です。
const name = 'yasuda'; //constは、後で書き換えることができない変数宣言です。
console.log(name + 'さん、こんばんは');

letは、後で書き換えることができる変数宣言です。

constは、後で書き換えることができない変数宣言です。

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

javascriptのonclickとaddEventlistnerの違い

まずonclickとaddEventlistnerを書いてみる!!

(js)のonclick

<p id ="click">btn</p>


<script>
document.getElementById("click").onclick=function(){
    console.log("clicked");
}

</script>

これでconsole.log();でうまく表示させることができました。

しかし、下記のように書いてみると,,,,,,,,

<p id ="click">btn</p>


<script>
document.getElementById("click").onclick=function(){
    console.log("clicked");
}

document.getElementById("click").onclick=function(){
    console.log("two-clicked");
}

</script>

だと、
console.log("clicked");の方は表示されなくて下記の方のconsole.log("two-clicked");の方は表示されました。
つまりどういうことかと言いますと、『上書きされた』と言うことになります。

では、次にaddEventlistnerの方を見ていきましょう!!

変数名.addEventListener("click",function(){};

<p id ="click">btn</p>

<script>
let on = document.getElementById("click");
on.addEventListener("click",function(){
    console.log("cliked");
})

</script>


このように書くと、console.log("cliked");が表示されるようになります。

これも次のように書いていくと、、、

<p id ="click">btn</p>

<script>
let on = document.getElementById("click");
on.addEventListener("click",function(){
    console.log("cliked");
})

on.addEventListener("click",function(){
    console.log("two-clicked");
})


</script>



このように書くと、 console.log("cliked"); と console.log("two-clicked");の両方が、
表示されるようになります。

まとめ

onclickは上書きされる

変数名.addEventListener("click",function(){}; は、上書きされずに反映される。

なので、onclickとaddEventlistnerの使い分けは状況によって使い分けた方が良さそうですね!!!

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

「ざっくり」iteratorとgeneratorを理解する

iterator(イテレータ)とは

iterableなオブジェクトの中で動作する仕組みのこと。
iterableなオブジェクトが連続して順番に値を返すための機能のこと。

iterable(イテラブル)とは

iteratorを内蔵しているということ。
変数名[Symbol.iterator]の中にiteratorがあるかどうか確認すれば、そのオブジェクトがiterableかを判別できます。

const arr = [0, 1, 2];
const str = 'hoge';
const obj = {a: 1, b: 2, c: 3};

console.log('Is iterable? => ' + !!arr[Symbol.iterator]);
console.log('Is iterable? => ' + !!str[Symbol.iterator]);
console.log('Is iterable? => ' + !!obj[Symbol.iterator]);

他にもざっくりとしたイメージだと

  • 連番管理されているもの(あくまでもイメージ)
  • for...ofで回るもの

といった具合です。

iterableなオブジェクトの例

  • String
  • Array
  • HTMLCollection
  • NodeList

generator(ジェネレータ)とは

iteratorを作るもとです。
次のようなルールでgenerator関数を作成できます。通常の関数とかなり振る舞いが違うことに注意してください。

  • functionのあとに*を書く
  • yieldキーワードを使う

yield(イールド、イェルドゥ)とは

  • generator関数の中でreturn文の代わりに使う
  • yieldキーワードの隣に返したい値を記述する
  • 一度読み込まれたyieldは同じiteratorでは決して読まれない

実際に動かしてみる

const generator = function* () {
  yield 'h';
  yield 'o';
  yield 'g';
  yield 'e';
};
const iterator = generator(); // generatorからiteratorを生成

console.log(iterator.next().value); // > h
console.log(iterator.next().value); // > o
console.log(iterator.next().value); // > g
console.log(iterator.next().value); // > e
console.log(iterator.next().value, iterator.next().done); // > undefined true

変数iteratorには、nextメソッドを持つオブジェクトが入っています。
next()メソッドは実行されると、次のような構造を持つオブジェクトを返します(1回目)。

{
  "done": false,
  "value": "h"
}
  • doneは、すべてのyieldが読み込まれたらtrueになる
  • valueは、next()が呼ばれた順に先頭からyieldの返り値が入ってくる

最終的には次ような値を返すようになります。

{
  "done": true,
  "value": undefined
}

yield*式

yield*式は、いっぱいyieldを書かなくてもよくなるものというイメージで最初は良いと思います。
正確には、iteratorが呼ばれ時に参照するオブジェクトを別のiterableオブジェクトで代替する(別のオブジェクトに委任する)ためのものです。

const generator = function* () {
  yield* 'hoge'; // Stringはiterable
};
const iterator = generator(); // generatorからiteratorを生成

console.log(iterator.next().value); // > h
console.log(iterator.next().value); // > o
console.log(iterator.next().value); // > g
console.log(iterator.next().value); // > e
console.log(iterator.next().value, iterator.next().done); // undefined true

または

const generator = function* () {
  yield* ['h', 'o', 'g', 'e']; // Arrayはiterable
};
const iterator = generator(); // generatorからiteratorを生成

console.log(iterator.next().value); // > h
console.log(iterator.next().value); // > o
console.log(iterator.next().value); // > g
console.log(iterator.next().value); // > e
console.log(iterator.next().value, iterator.next().done); // undefined true

generator関数内の変数は計算内容が継続される?

yield文はgenerator関数(正確にはiterator)が呼ばれた順番に値を返すのと同時に中断と再開の役割をもっています。

const generator = function* () {
  console.log(0);
  yield 'h';
  console.log(1);
  yield 'o';
  console.log(2);
  yield 'g';
  console.log(3);
  yield 'e';
  console.log(4);
};
const iterator = generator(); // generatorからiteratorを生成

console.log(iterator.next().value); // > 0 h
console.log(iterator.next().value); // > 1 o
console.log(iterator.next().value); // > 2 g
console.log(iterator.next().value); // > 3 e
console.log(iterator.next().value, iterator.next().done); // > 4 undefined true

yieldの間に挟まっているconsole.log()がiteratorの呼び出しに応じて走っていることから、generator関数内のコードの途中から処理が再開している様子がわかります。

yield文で中断された計算内容がgenerator関数では保持されているように見えますね。

次の例を見てみましょう。

// ただの関数
const foo = function () {
  let index = 0;

  while (index < 3) {
    return index++;
  }
};

// 何度やっても 0 になる。当たり前。
console.log(foo()); // > 0
console.log(foo()); // > 0
console.log(foo()); // > 0
// generator関数
const foo = function* () {
  let index = 0;

  while (index < 3) {
    yield index++;
  }
};
const iterator = foo(); // ★

// whileの途中のindexの計算値が帰ってくる
console.log(iterator.next().value); // > 0
console.log(iterator.next().value); // > 1
console.log(iterator.next().value); // > 2

実は実際のところ、generator関数の処理が中断・再開されているように感じているだけなのですが、最初は驚くかもしれません。

勘違いしやすいかもしれないですが、「★」の行でgenerator関数は1度実行され、別のオブジェクトを生成しています。この時点でindexのインクリメントも、while文も計算し終わっています。

もし改めてgenerator関数fooを呼び出して新しいiteratorを作れば、再び変数indexの値は0から始まります。

もともとオリジナルのiteratorを持ったオブジェクトを作る

あとからオブジェクトにiteratorを生やすには次のように書けます。

const obj = {}; // ただのオブジェクト

obj[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

const iterator = obj[Symbol.iterator](); // イテレーターを手動で初期化

// 手動で呼び出し
console.log(iterator.next().value); // > 1
console.log(iterator.next().value); // > 2
console.log(iterator.next().value); // > 3
console.log(iterator.next().value, iterator.next().done); // > undefined true

// for...ofが初期化から呼び出しまで自動でやってくれている
for (const item of obj) {
  console.log(item); // h o g e
}

初回のオブジェクト定義の段階でも定義できます。

const obj = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  },
  hoge: 'piyo',
  foo() {
     return 100;
  }
};

for (const item of obj) { 
    console.log(item); // > 1 2 3
}

Example

たとえば、NodeListfor..ofで回してみましょう。

const anchors = document.querySelectorAll('a');

for (const item of anchors) {
   console.log(item);
}

普通に回ったと思います。

次に、このNodeListのiteratorを破壊して別の値が出るようにしてみます。

// 適当な文字列でiteratorを破壊
const anchors = document.querySelectorAll('a');

anchors[Symbol.iterator] = function* () {
  yield* ['a', 'b', 'c'];
};

for (const item of anchors) {
   console.log(item);
}

a要素が入ったNodeListfor...ofしたはずなのに、「a」「b」「c」が出力されました。
yield*式ではなくyieldで行う場合は次のようにも書けます。

// 適当な文字列でiteratorを破壊
const anchors = document.querySelectorAll('a');

anchors[Symbol.iterator] = function* () {
  const arr = ['a', 'b', 'c'];
  let index = 0;

  while (index < arr.length) {
    yield arr[index++];
  }
};

for (const item of anchors) {
   console.log(item);
}

もともとのNodeListのiteratorを破壊しつつも、同じ振る舞いをするように書き換えると、次のように書けます(非常に無意味ですが)。

// 標準のiteratorと同じこと
const anchors = document.querySelectorAll('a');

anchors[Symbol.iterator] = function* () {
  let index = 0;

  // generator関数内の`this`は、最終的にiteratorを持つオブジェクトを参照します。
  while (index < this.length) {
    yield this[index++];
  }
};

for (const item of anchors) {
   console.log(item)
}

おまけ1:iterableになったオブジェクトはスプリット構文が使える

不思議な感じがしますが、[object Object]も分割できます。スプリット構文もiterableかどうかが重要だったんですね。

const obj = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  },
  hoge: 'piyo',
  foo() {
     return 100;
  }
};

console.log(...obj); // > 1 2 3
console.log([...obj]); // > (3)[1, 2, 3]

おまけ2:jQueryオブジェクトをiterableにする1

jQueryでは、要素を常に連番で管理します。jQuery内では.get(0)メソッド2.eq(0)メソッド3で個別に取得するのが一般的な方法ですが、ブラケッツ(各括弧)で連番を入れても該当する1つの要素を取ることができます。

console.log($('a')[0]); // 最初のa要素

一見NodeListHTMLCollectionのようなふるまいですが、実はどちらでもありません。キー名が連番のため配列のように見えるオブジェクト、これをjQueryオブジェクトと呼んだりします。

jQueryオブジェクトはiterableではありません。for...ofに無理やり入れると、次のようにエラーが吐かれます。

const $a = $('a');

for (const elm of $a) { // > $a is not iterable
  console.log(elm);
}

これまで見てきた方法でjQueryオブジェクトにiteratorを生やしてみます。

const $a = $('a');

$a[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

for (const elm of $a) {
  console.log(elm); // > 1 2 3
}

とりあえず回りました。あとは1つ1つyieldが正しくjQueryの持つ要素を返せるようにgeneratorの中身を書き換えるだけです。

const $a = $('a');

$a[Symbol.iterator] = function* () {
  let index = 0;
  const {length} = this; // thisはjQueryオブジェクトを指します

  while (index < length) {
    yield this[index++];
  }
};

for (const elm of $a) {
  console.log(elm); // 要素が1つずつコンソールへ出力される
}

無事に回すことができました。とはいえ、これは練習で回せただけだということにご注意ください。
現実的にjQueryオブジェクトをfor...ofするにはjQuery本体でjQueryオブジェクトにiteratorが生える機能が実装されるしかないですし、jQueryにはほぼ同じ振る舞いをする.each()メソッドが用意されていますので、jQueryオブジェクトを回したいときにはこちらを利用したほうがいいですね(IEサポートもありますし)。

まとめ

そんな感じで、任意のオブジェクトに自分の意図した値を渡しながら反復処理をさせられるのがiteratorとgeneratorです。

yieldで返せる値は様々なので、呼び出すたびに異なる関数を順番に返すパターンなんかもありそうですね。

あるいは独自のclassを実装したときに、生成したインスタンスでjQueryのように連番で保持する何かしらの情報をネストさせずにiterableにしたいときに有効なんでしょうか(使いどころピンと来てない顔)(iterator使いこなせるマンの方ぜひ使いどころをご教授ください)。

うまく使いこなせれば、簡潔で一貫性のある見渡しのいいコードが書けるようになるかもしれないですね。


  1. jQueryが読み込まれている環境前提 

  2. 返り値は0番目の要素オブジェクト 

  3. 返り値は0番目の要素だけを持ったjQueryオブジェクト 

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

アクティブなタブを切り替える度にそのタイトルを取得しGoogleスプレッドシートに書き込むfirefoxアドオンを作った話

概要と、うだうだした話

サークルで一時流行したSlackBot開発も下火になる中、未だに密かにBotに機能を追加しようと開発を続けている者がいた……
この前サークルでの「SlackBot開発によって得られた知見共有会」にて「大学生はプライベートをネタにしてなんぼ」とか息巻いて今後の展望として「今何見てる機能(他の人からメンションを受けると現在見ているサイトをSlackに晒す)」とかを実装すると言いましたそれを実装した際に作った「アクティブなタブを切り替える度にそのURLを取得するfirefoxアドオン」について書きたいと思います。
 そもそも、firefoxのアドオンの作り方について解説してくれてる日本語のサイトが少ないんですよね。Chromeの拡張機能の作り方サイトはかなり充実しているのにfirefoxアドオンでググっても出てくるのは「firefoxアドオン使えなくなる」とかばっかり。firefoxユーザーってそんな少ない訳でも無いでしょうに。というわけでアドオンを作成したい人が参考に出来るようにかなり丁寧に説明しようと思います。

実装の概説

まずGASでGETを受けたら受け取った内容をスプレッドシートに書き込むスクリプトを作る。
firefoxアドオン側でfirefoxのアクティブなタブが切り替わる度にアクティブなタグの名前を取得しスクリプトに向けてGETを飛ばす。

必要なファイル群

今回は閲覧しているページとかを見て何かする訳では無いのでcontent_scriptsは必要ではなくmain.jsとmanifest.jsonにアイコンファイルだけです。この辺はChrome拡張と似てますね。

mainjs
function getCurrentWindowTabs() {
  return browser.tabs.query({currentWindow: true, active: true});
}
function handleCreated(tab) {
  getCurrentWindowTabs().then(tabs => {str=tabs[0].title});
  var url; // リクエスト先URL
  url=url + "?name="+str
  var request = new XMLHttpRequest();
  request.open( "GET", url, true );
  request.send(null);
  request.onreadystatechange = function () {
    if(request.readyState == 4){
          if(request.status == 200){
             console.log(str+'を見てます。');
          }
        }
};
}
browser.tabs.onActivated.addListener(handleCreated);
manifest.json
{
  "manifest_version": 2,
  "name": "Tab URL",
  "version": "1.0",
  "description": "Tab watcher",
  "icons": {
    "48": "icon.png"
  },
  "background": {
    "scripts": ["main.js"]
  },
"permissions": [
"tabs"]

}

main.jsについて、
アクティブなタブを取得する方法が分からなかったのですがこちらのサイト (hawksnowlog: 現在開いているタブの URL を取得する方法) に書いてあったのを参考にしました。要するにbrowser.tabs.queryでタブを取得するとアクティブなタグが配列の一番初めに格納されるらしい。.titleにすることでこのタブのサイトのタイトルを取得出来ます。
tabs.onActivatedがウィンドウ内でアクティブなタブが変わったときに発火するイベントですのでここにリスナーを追加します。

GASの用意

まず新規のGoogleスプレッドシートを作成し「ツール」から「スクリプトエディタ」を開きます。そこに以下のコードをコピペ

function doGet(e) {
  var ss       = SpreadsheetApp.getActiveSpreadsheet();
  var sheet    = ss.getSheetByName('シート1');
  var name=e.parameter.name
  sheet.getRange(1, 1).setValue(name);
  return ContentService.createTextOutput("OK");
}

これでGETを受けた時nameの値をA1のセルに書き込みます。
これで「公開」から「ウェブアプリケーションとして公開」、「Execute the app as:」にはMeを「Who has access to the app:」にはAnyone, even anonymousを選択でデプロイするとURLが発行されるのでこのURLをさっきのmain.jsのリクエスト先URLに代入しておきましょう。

アドオンを発行する方法

さてこのアドオンを自分の環境に読み込ませおきたい時about:debuggingでもいいんですがこれだと一時的な読み込みしかできません。firefoxはchromeと違ってパッケージ化して署名をもらわなければいけません。

まず、manifest.jsonとmain.jsにicon.pngを一つのzipに突っ込みます。
zipの拡張子をxpiに書き換えます。
https://addons.mozilla.org/ja/developers/ここから開発者登録を行い、xpiファイルをアップロードします。
配布手段で「自分自身」の方を選択したら審査なしに即座に使えるようになります。

結果

やってみたら分かると思うんですけどこれ実行すると今見てるタグというよりタグを切り替える前に見ていた一つ前のタグが送信されているみたいなんですよね。多分イベントが発火するのが早いのが原因だと思うのでhandleCreatedに少しだけ待機する処理をかませるといいかもしれませんね。
またタブの切り替えだけでなくページが切り替わった時も取得したい時はtabs.onUpdatedにもリスナーを追加すればいいのでしょうかね。

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

Vue.js でマインスイーパっぽいものを作ってみた

?初めに

コーディングの練習のために、個人的に慣れ親しんでいる Vue.js で単純な実装のゲームとされるマインスイーパを作ってみました。

マインスイーパの仕様をきちんと調べたわけではないため、動きに若干不安がありますが、そのあたりは漸次改善していく予定です。

?本題

環境

バージョン
Node v12.11.1
npm 6.11.3
vue-cli 4.0.5

成果物

それっぽい動きしています。

minesweeper.gif

ソースコード(github)

実装のポイント

マインスイーパーの各マスを作る

          <template v-for="(cell, yi) in row">
            <td
              v-if="started"
              :key="yi"
              class="cell cell--started"
              :class="{ 'cell--opened': cell.isOpen }"
              @click="open(xi, yi)"
              @click.right.prevent="mark(xi, yi)"
            >
              <span v-if="cell.isOpen">
                {{ cell.isBomb ? "?" : cell.bombCount === 0 ? "" : cell.bombCount }}
              </span>
              <span v-if="cell.isMark">
                ?
              </span>
            </td>
            <td
              v-if="!started"
              class="cell cell--unstarted"
              :key="yi"
            >
              &nbsp;
            </td>

マインスイーパーの盤面自体は table 要素で作っています。
地雷が配置される各マスは、td 要素で表現しています。
v-if によりフラグを切り替えることで、以下の状態を切り替えています。

  • そもそもゲームが開始されているかどうか
  • マスが開かれているかどうか
  • 旗が立てられているかどうか

また以下のように、マスをクリックすることで、マスを開いたり旗を立てたりする処理を発火させるようにしています。

  • 左クリック ... マスを開く処理を発火する
  • 右クリック ... 旗を立てる処理を発火する

地雷を配置する

    /**
     * 地雷を配置します.
     */
    _layMines() {
      // ランダムな整数値を返す
      const getRandomInt = max => {
        return Math.floor(Math.random() * Math.floor(max));
      }
      for (let i = 0; i < this.numOfBomb; i++) {
        const x = getRandomInt(this.maxX);
        const y = getRandomInt(this.maxY);
        // 既に地雷が置かれている場合、別のセルに置くようにする
        if (this.area[x][y].isBomb) {
          i--;
          continue;
        }
        this.area[x][y].isBomb = true;
        // 地雷に隣接するセルの周囲の地雷数を増やす
        // 上のセル
        if (x-1 >= 0) {
          this.area[x-1][y].bombCount++;
          if (y-1 >= 0) this.area[x-1][y-1].bombCount++;
          if (y+1 < this.maxY) this.area[x-1][y+1].bombCount++;
        }
        // 横のセル
        if (y-1 >= 0) this.area[x][y-1].bombCount++;
        if (y+1 < this.maxY) this.area[x][y+1].bombCount++;
        // 下のセル
        if (x+1 < this.maxX) {
          this.area[x+1][y].bombCount++;
          if (y-1 >= 0) this.area[x+1][y-1].bombCount++;
          if (y+1 < this.maxY) this.area[x+1][y+1].bombCount++;
        }
      }      
    }
  }

設定された地雷の数だけ繰り返して地雷を盤面に設置しています。
ランダムな x 行目・ y 列目を取得し、その x 行 y 列のマスにボムであるフラグを立てます。
フラグを立てた後、周囲8マスの「周囲に地雷がある数」を +1 させています。

マスを開く

    /**
     * セルを開きます.
     * @param {Number} x 何行目.
     * @param {Number} y 何列目.
     */
    open(x, y) {
      // ?だったら終了
      if (this.area[x][y].isBomb) {
        alert("Bomb!!!");
        for (let i = 0; i < this.maxX; i++) {
          for (let j = 0; j < this.maxY; j++) {
            this.area[i][j].isOpen = true;
          }
        }
      }
      this.area[x][y].isOpen = true;
      if (this.area[x][y].bombCount !== 0) return;
      // 周囲に地雷がない空きセルを開く
      // 左のセル
      if (x-1 >= 0 && this.area[x-1][y].autoOpenable()) {
        this.open(x-1, y);
      }
      // 右のセル
      if (x+1 <= this.maxX-1 && this.area[x+1][y].autoOpenable()) {
        this.open(x+1, y);
      }
      // 上のセル
      if (y-1 >= 0 && this.area[x][y-1].autoOpenable()) {
        this.open(x, y-1);
      }
      // 下のセル
      if (y+1 <= this.maxY-1 && this.area[x][y+1].autoOpenable()) {
        this.open(x, y+1);
      }
    },

マスを開く処理では、単にクリックされたマスを開くとともに、「周囲に一つも地雷がない」隣接するマスを再帰的に開いていく処理を行っています。

具体的には、以下のような再帰処理を行っています。
1回目に実行される(A)では、クリックされたマスに対して判定処理を行います。

  1. (A)マスの上下左右を開くことができるか判定する。
    1. 開くことができる場合
      1. マスを開く。
      2. 開いたマスに対して、(A)の処理を行う。
    2. 開くことができない場合
      1. 終了。

マスを開くことができるか判定する処理は、以下の通りです。

  /**
   * 自動で開くことができるか判定して返します.
   * @return {Boolean}
   */
  autoOpenable() {
    return !this.isOpen && !this.isBomb;
  }

旗を立てる

    /**
     * セルに旗を立てたり、立てなかったりします.
     * @param {Number} x 何行目.
     * @param {Number} y 何列目.
     */
    mark(x, y) {
      if (!this.area[x][y].isOpen) {
        this.area[x][y].isMark = !this.area[x][y].isMark;
      }
    },

旗を立てる処理はごく単純で、マスがまだ開かれていない場合、旗を立てるか立てないかを切り替えるようにしています。

?おわりに

今回はマインスイーパ(?)を作りましたが、今後はテトリスやぷよぷよも作ってみたいと思います。
Vue.js で作るのもいいですし、生のJSやSVG、canvasで作るのも面白そうです。

参考文献

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

JavaScriptの基本 1

はじめに

この記事は、2019/11/1からプログラミング学習を始めた学生がプロのプログラマーになるまでのアウトプットする為の書き込みの場である。

JavaScript

DOMとはDocument Object Model(ドキュメントオブジェクトモデル)

  1. DOMツリーからノードを取得する
  2. JavaScriptでやりたい処理を書く
  3. イベント発火でHTML側で動かす

JavaScript書き方

  • document.getElementById("id名");
    • マッチするidを持つノードを取得することができます。
  •  document.getElementsByClassName("class名");
    • クラス名とマッチするデータを取得することができます。
  • document.querySelector("セレクタ名");
    • HTML上から、引数で指定したセレクタに合致するもののうち一番最初に見つかった要素1つを取得します。
    • DOMの取得とは、querySelectorメソッドなどを使用して、HTML要素をノードとして取得することです。
  •  addEventListener
    • あるノードオブジェクトに対して、イベントリスナを追加するメソッドです。
  • innerHTML
    • HTML要素の中身を書き換えることができます。
  • classList.add

    • クラスが追加されるようにclassList.addを利用します。
  • classList.remove
    • 
クラスを削除するclassList.removeを利用します。
  • Array.prototype.slice.call()
    • 引数にとったオブジェクトを配列に変換してくれます。
  • forEach()
    • 配列に対してよく使われる繰り返し処理です。
  • indexOf()
    • 配列に対してだけ使い、DOMを引数にとって一致した要素番号を戻します。

今日の感想 明日から

今日は、インプットに集中した結果、JavaScriptの基礎、jQueryの基礎を学んだ。
アウトプットについては、基礎的な事ができるまでこのまま毎日投稿するが、簡単で端的にアウトプットしていこうと思います。

終わりに

もっとこうした方が良いよなどご指摘頂けると幸いです

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

ウィンドウを閉じるときに任意のメッセージを設定したダイアログを表示する事ができなかった記録

注意:このページで「ウィンドウを閉じるときに任意のメッセージを設定したダイアログを表示する」方法はわかりません。

  • 動作確認した環境
    • MacOS Mojave 10.14.6
    • Google Chrome 78.0
    • Fire Fox 70.0
    • Safari 13.0

やりたいこと

ブラウザにおいてユーザが[x]ボタンやCtrl + Wでウィンドウを閉じる時に任意のメッセージを表示した[OK][キャンセル]ボタンがあるダイアログを表示したい。

ボタンのイベントでは似たようなことがよくあるので難しいと思っていなかった・・・。

サイト側でウィンドウを閉じられなくするなんてセキュリティ的に問題になるよね、ってことに気がつくのが遅かったのです。
MacのChrome
a.gif

sample.js
/** [閉じる]ボタン押下処理. */
function onClickClose() {
    let result = confirm('本当に閉じていいの?');
    if (result) {
        window.close();
    }
}
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="content-type" charset="utf-8">
    <title>ウィンドウを閉じるときにメッセージダイアログを表示したい</title>
    <script src="https://code.jquery.com/jquery-1.11.0.min.js" integrity="sha256-spTpc4lvj4dOkKjrGokIrHkJgNA0xMS98Pw9N7ir9oI=" crossorigin="anonymous"></script>
    <script src="sample.js"></script>
</head>
<body>
    <table>
        <tr><td>入力欄</td><td><input id="pasteArea" type="text"></td></tr>
        <tr><td colspan="2"><input type="button" id="close" value="閉じる" onclick="onClickClose();"></td></tr>
    </table>
</body>
</html>

ウィンドウを閉じるのは、beforeunloadイベントでキャッチできる

Window: beforeunload イベント - Web API | MDN

beforeunloadイベントではconfirm()が使えなかった

HTML 仕様書は window.alert(), window.confirm(), window.prompt() などのメソッドが、このイベントの実行中には無視されることがあることを示しています。
Window: beforeunload イベント - Web API | MDN

MacのFireFox
a.gif

sample.js
$(window).on('beforeunload', function(e) {
    // ウィンドウを閉じる時にメッセージを表示する.
    let result = confirm('本当に閉じていいの?');
    return result;
});

イベントの返却値に任意のメッセージを設定してもダイアログには表示されない

MacのSafari
a.gif

$(window).on('beforeunload', function(e) {
    // ウィンドウを閉じる時にメッセージを表示する.
    return '本当に閉じていいの?';
});

preventDefault()を呼び出したけれどダイアログには表示されない

仕様書によれば、確認ダイアログを表示するためにはイベントハンドラーでイベントの preventDefault() を呼び出すことになっています。
Window: beforeunload イベント - Web API | MDN

MacのChrome
a.gif

sample.js
$(window).on('beforeunload', function(e) {
    e.preventDefault();
    // ウィンドウを閉じる時にメッセージを表示する.
    return '本当に閉じていいの?';
});

returnValueに任意のメッセージを設定してもダイアログには表示されない

MacのFireFox
a.gif

sample.js
$(window).on('beforeunload', function(e) {
    e.preventDefault();
    // ウィンドウを閉じる時にメッセージを表示する.
    e.returnValue = '本当に閉じていいの?';
    return '';
});

そもそも、IE以外のいろんなブラウザがカスタムメッセージへの対応をやめている

画像参照元 : Window: beforeunload イベント - Web API | MDN
スクリーンショット 2019-11-24 16.38.14.png

自作のダイアログが使えるか試してみる

カスタムメッセージが表示できないのなら、自作のダイアログを表示できないかを試してみます。

jQuery UIでダイアログをとりあえず自作してみる

Dialog | jQuery UI 1.10 日本語リファレンス | js STUDIO
a.gif

sample.js
...省略...
$(window).on('load', function() {
    createDialog();
});

/** 自作ダイアログを設定する. */
function createDialog() {
    $('#mydialog').dialog({
        title: '本当に閉じていいの?',
        autoOpen: false,
        modal: true,
        height: 0,
        width: 200,
        closeText: 'x',
        buttons: [
            {text: '閉じない', click: function() {
                $(this).dialog('close');
            }}
        ]
    });
}

/** 自作ダイアログ押下処理. */
function onClickMyClose() {
    $('#mydialog').dialog('open');
}
...省略...
        <tr><td colspan="2"><input type="button" id="close" value="閉じる" onclick="onClickClose();"></td></tr>
        <!-- ↓ここを追加 -->
        <tr><td colspan="2"><input type="button" id="myClose" value="自作ダイアログ" onclick="onClickMyClose();"></td></tr>
    </table>
...省略...

ウィンドウを閉じるときに自作ダイアログを表示したがうまくいかなかった

ブラウザのダイアログが出てしまう

MacのFireFox
a.gif

sample.js
$(window).on('beforeunload', function(e) {
    e.preventDefault();
    // ウィンドウを閉じる時にメッセージを表示する.
    $('#mydialog').dialog('open');
});

ブラウザのダイアログを出さないとウィンドウが閉じてしまう

MacのFireFox
a.gif

sample.js
$(window).on('beforeunload', function(e) {
    // ウィンドウを閉じる時にメッセージを表示する.
    $('#mydialog').dialog('open');
});

そもそも、ウィンドウを閉じるイベントをキャンセルする方法がわからない

たとえ、タイマーかななんかで自作ダイアログを表示できたとしても「ウィンドウを閉じるイベントをキャンセルする方法がわからない」という根本的な事に気がついた:sob:

参考情報

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

コードを和訳する②(chomp,function,def)

はじめに

前回の記事が思いのほか書くのが楽しかったので、続けざま書いてみます。

実行

まずはRubyの次のコードです。

num = gets.chomp.to_i

入力した数字をnumに代入するコードです。
このうちgets理解する、入手するといった意味があります。
そしてchompむしゃむしゃ食べるという意味があります。
このコードは、言ってみれば人間の言葉を機会の言葉に翻訳する処理とも言えるので、
入力された人間の言葉を咀嚼して、機械にも理解できるようにするという意味合いだと予想できます。

続いては、JavaScriptの関数に使われているfunctionです。

function Say(Hello){
  console.log(Hello);
}

といった具合に使われます。

functionはそのまま関数、機能という意味があるので、特に解釈の必要はなさそうです。

ではRubyの方で関数定義するときに使うdefはどうでしょうか?

def Say(Hello){
 puts Hello
end

という風に使われます。

def自体の意味はすばらしいという意味で、このままイマイチしっくりこないので、defで始まる単語の略であるという説をもって検索してみます。
するとdefinite :確定するという単語が見つかりました。
ここから解釈すると、前述の関数はSayは~という処理と決めるという意味合いだと想像できます。

結果

defなどは若干無理矢理感がありましたが、実際に単語の意味を調べてみると自分なりに理解ができそうです。
まだ短いコードでしか和訳を試していたないので、いずれはもっと長いコードの和訳に挑戦してみたいです。

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

コードを和訳する①(Hello World)

はじめに

プログラミング言語というのは大体英語です。
こういう記事(Qiita外記事)があるくらい、とにかく英語で書かれています。
割と簡単な単語もあれば、辞書を引かないとわからないような単語もあります。
そして、実際に意味を調べると、コードの意味がよくわかることもあります。

本記事では、過去の記事で書いたコードにある英単語を和訳してみて、解りやすくなるのか…わけがわからなくなるのか…それとも笑える感じになるのか、そういったことを試してみます。

実行

どんなプログラムでも最初に勉強することはHello Worldと出力することですが、RubyとJavaScriptではそれぞれ次のようにコードを書きます。

puts "Hello World"
console.log(Hello World)

まずputsの方ですが、goo辞書で調べてみると暴動、反乱といった単語がでます。
明らかに違います
そこで、putの複数形でputsという説を立て再調査してみると、置く、移動する、言う、表現する etcという意味が出てきます。
この中では言う、表現するあたりがしっくりきます。

同じようにconsole.logの方を調べてみます。
consoleの方は、動詞だと慰める、名刺だと端末といった意味が出てきました。
このままではピンとこないので、logも調べてみます。
logの意味は色々ありますが記録に関する意味があるようです。

これらをふまえて、前述のコードを翻訳してみると、

言う "Hello World"
端末の記録(Hello World)

rubyの方はHello Worldと言うという風に解釈できそうです。
JavaScriptの方は端末(=()の中身)の記録を出すかなり強引にに解釈できそうです。

結果

JavaScriptの方はかなり強引になってしまいましたが、Rubyの方は割としっくりくる感じです。
Rubyは日本人が作った言語というのも、こういう結果に影響しているのかもしれません。
JavaScriptの方は英語ではないのでは?という疑念が沸々と湧いています。

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

ブラウザ動作ゲーム開発で良さそうライブラリ

ちょっと触ってみて、良さそうなものをメモ

名称 サイトURL メモ
PixiJS https://www.pixijs.com/ アニメーションや変形・色変が強そう
CREATE JS https://www.createjs.com/ 音系があるので音の場合良さそう
Matter.js https://brm.io/matter-js/ 物理演算系いい感じ

ゲーム系ライブラリは PixiJS や CREATE JS が強そうですが 
物理演算系は matter-js 良さそうかも

PixiJS

NoName_2019-11-28_20-19-11_No-00.png

スクラッチで除去(pixijs)
https://pixijs.io/examples/#/demos-advanced/scratchcard.js

CREATE JS

NoName_2019-11-28_20-19-28_No-00.png

ヨコ走り系(createjs)
https://www.createjs.com/demos/easeljs/spritesheet

シューティング系(createjs)
https://www.createjs.com/demos/easeljs/game

雲り除去(createjs)
https://www.createjs.com/demos/easeljs/alphamaskreveal

Matter.js

NoName_2019-11-28_20-18-58_No-00.png

スリングショット系(matter-js)
https://brm.io/matter-js/demo/#slingshot

人気のゲームエンジンらしい(ほんまもの向け)

 https://www.egret.com/en/case
 https://cocos2d-x.org/filecenter/jsbuilder/

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

DOMについて調べたことの備忘録

 DOMについて

  • Document Object Model
  • HTML や XML 文書を操作するための、たくさんの機能や規則のこと
  • DOMは階層構造をとる
  • 各要素はノードという単語を用いて表現される

ノードについて

  • ノード
  • 子ノード(children)
  • 親ノード(parent)
  • 兄弟姉妹ノード
  • ノードは、webページとプログラミング言語をつなぐ役割を持つ
  • ID名からノードを取得、操作する

JavaScript初心者でもすぐわかる!DOMとは何か?

DOMとは何か?【JavaScript初心者向けにわかりやすく説明します!】

DOMの話に戻ります

HTML要素の取得:getElementById()

  • Documentオブジェクトのメソッドである「getElementById()」が一般的らしい
  • 文字列のHTMLタグを認識できる

使用例

<body>
<p id="text">これはサンプルテキストです</p>

<script>
    var p = document.getElementById('text');

    console.log( p );
</script>
</body>

実行結果

<p id="text">これはサンプルテキストです</p>

HTML要素をJavaScriptから書き換える:innerHTMLプロパティ

  • 「innerHTML」プロパティを代入するだけ!

使用例

<div id="wrap">
    <p>これはサンプルテキストです</p>
</div>

これをinnerHTMLプロパティでp要素をh1要素に書き換える

var div = document.getElementById('wrap');

div.innerHTML = "<h1>サンプルタイトル</h1>";

console.log( div );

実行結果

<div id="wrap">
    <h1>サンプルタイトル</h1>
</div>

JavaScriptからHTML要素を作成する:createElement( )

  • もっとも簡単な方法はcreateElement()
  • 例えば「createElement('p')」と記述することで「p要素」を新規に生成
var pElement = document.createElement('p');
  • この方法で作成しても、画面に表示されないので、HTML要素をDOMに追加しなくてはならない

HTML要素を追加:appnedChild()

  • JavaScript側で生成したHTML要素をDOMに追加
var p = document.createElement('p');

//テキストを追加する
p.textContent = 'これはサンプルです';

//body要素内にp要素を配置する
document.body.appendChild( p );

実行結果

<p>これはサンプルです</p>

※ textContent()メソッドでテキストを追加

DOMが読み込まれるタイミング:onloadイベント

  • DOMによるオブジェクト化がされていないと、JavaScript側からHTMLを操作できない
  • DOMが読み込まれた後にJavaScriptが実行できるようにしなければならない
  • その中で、もっとも簡単なのがonloadイベント
dow.onload = function() {

    //ここに処理を書く

}
  • しかし複数記述すると、その都度上書きされてしまうので、上に書いたものは実行されない・・・
window.onload = function() {
//処理1
}

window.onload = function() {
//処理2
}

window.onload = function() {
//処理3
}
  • こちらの処理は一番下しか実行されない

onloadでなくaddEventListenerを使ってみる

使用例

window.addEventListener('load', function() {
//処理1
})

window.addEventListener('load', function() {
//処理2
})

window.addEventListener('load', function() {
//処理3
})
  • 第一引数にloanを書き、第二引数に実行する関数処理をかく
  • addEventListenerを使用することで、3回記述したとしても全て実行することができる

【JavaScript入門】はじめてのDOM操作・取得まとめ!

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

コメントの削除も非同期で対応する

コメント投稿の非同期は成功しました。
そこで、コメント削除も非同期にて対応しようと思い。
クロームの検証画面でエレメントコピーして修正して下記コードを追加しました。
削除

コメントして即削除でActiveRecord::RecordNotFound in TweetsController#destroyが出ます。

一度リロードをすれば問題なく削除できているので
非同期部分で引っかかっているのは確認済みです。

どうすればいいのでしょうか?
コメント投稿と削除で別々の
コードがいるのでしょうか?
記載をするべきでしょうか?
何かファイルを増やしてそこに記載がいるのでしょうか?

$(function(){
  function buildHTML(comment){
    var html = `<p>
                  <strong>
                    <a href=/users/${comment.user_id}>${comment.user_name}</a>
                    :
                  </strong>
                  ${comment.text}
                  <a class="comment-delete" rel="nofollow" data-method="delete" href="/tweets/${comment.tweet_id}">削除</a>
                </p>`
    return html;            
  }


  $('#new_comment').on('submit',function(e){
    e.preventDefault();
    console.log(this)
    var formData = new FormData(this);
    var url = $(this).attr('action')
    $.ajax({
      url: url,
      type: "POST",
      data: formData,
      dataType: "json",
      processData: false,
      contentType: false
    })
    .done(function(data){
      var html = buildHTML(data);
      $('.comments').append(html);
      $('.form_message').val('');
      $('.form__submit').prop('disabled', false);
    })
    .fail(function(){
      alert('error');
    })
  })
})
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

03. 円周率

03. 円周率

"Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."という文を単語に分解し,各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ.

Go

package main

import (
    "fmt"
    "regexp"
    "strings"
)

func main() {
    var src string = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
    var wlen []int
    rex := regexp.MustCompile("([a-zA-Z]+)")

    //  単語に分割
    words := strings.Split(src, " ")
    for _, word := range words {
        //  アルファベットのみを抽出
        match := rex.FindStringSubmatch(word)

        //  アルファベットの文字数を保存
        wlen = append(wlen, len(match[1]))
    }

    //  結果を表示
    fmt.Println(wlen)
}

python

# -*- coding: utf-8 -*-
import re

src = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
wlen = []

#   正規表現パターン定義(アルファベットのみ)
pattern = re.compile('([a-zA-Z]+)')

#   単語に分割
words = src.split(" ")
for word in words:
    #   アルファベットのみ抽出
    result = re.search(pattern, word).group()

    #   アルファベットの文字数を保存
    wlen.append(len(result))

#   結果を表示
print(wlen)

Javascript

var src = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
var wlen = [];

//  ループ版
var words = src.split(' ');
for (var i = 0; i < words.length; i++) {
    val = words[i].match(/[a-zA-Z]+/);
    wlen.push(val[0].length);
}
console.log(wlen);


//  forEach 版
var wlen = [];
src.split(' ').forEach(function (value) {
    this.push(value.match(/[a-zA-Z]+/)[0].length);
}, wlen)

console.log(wlen);

まとめ

以外と簡単と作ったが、他の人の結果を見て間違ってることに気づいた。 "." や "," は抜くのか。
アルファベット判別は少し悩んだが、正規表現で対応。Goの正規表現処理は遅いんだったかな?。

問題の円周率の意味が解った時は、なんとなく嬉しかった。w

トップ

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

02. 「パトカー」+「タクシー」=「パタトクカシーー」

02. 「パトカー」+「タクシー」=「パタトクカシーー」

「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ.

Go

package main

import (
    "fmt"
    "strings"
)

func main()  {
    var p1 string = "パトカー";
    var p2 string = "タクシー";
    var result string;

    //  文字を配列にする
    p1s := strings.Split(p1,"");
    p2s := strings.Split(p2,"");

    //  配列数ループ(「パトカー」に合わせる。同じ文字数だから・・・)
    for i := range(p1s) {
        //  各文字を1文字づつ連結
        result += p1s[i] + p2s[i];
    }

    //  結果を表示
    fmt.Println(result);
}

python

# -*- coding: utf-8 -*-
p1 = u"パトカー"
p2 = u"タクシー"
p1s = []
p2s = []

#   文字列を配列へ(「パトカー」に合わせる。同じ文字数だから・・・)
for i in range(len(p1)):
    p1s.append(p1[i])
    p2s.append(p2[i])

#   配列数ループ(「パトカー」に合わせる)
result = ""
for i in range(len(p1s)):
    # 各文字を1文字づつ連結
    result += p1s[i] + p2s[i]

#   結果を表示
print (result)

Javascript

let p1 = "パトカー";
let p2 = "タクシー";
var result = "";

//  文字を配列にする
let p1s = p1.split("");
let p2s = p2.split("");

//  配列数ループ(「パトカー」に合わせる。同じ文字数だから・・・)
for (var i = 0; i < p1s.length; i++) {
    //  各文字を1文字づつ連結
    result += p1s[i] + p2s[i];
}

//  結果を表示
console.log(result);

まとめ

どれも、イマイチ感がすごい。
他の人のソースを覗いてみる。

トップ

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

Webエンジニア業界に感じた違和感

私は18年間ほど企業向け製品開発の世界(SIer含む)にいました。
メインで使っていた言語はC++とC#です。

2014年にウェブスタートアップを数カ月手伝う経験があり、
フロントエンドの技術やWebフレームワークに興味を持ち、ウェブ系のカンファレンスに行くようになりました。

ウェブの技術は大変面白かったのですが、そこである大きな違和感を感じもしました。

カンファレンスで発表する人の中にはその道の有名人みたいな人がいて、ブログやTwitter、Githubなどで沢山フォロワーがついています。

常に数字や営業的な雰囲気に包まれている企業向け製品開発にはない、純粋に技術を楽しむ雰囲気がとても楽しかったです。

ですが、よくよく観察しているとWeb業界には「何が凄いのかよくわからないけど有名な人」もいました。

はっきり言ってしまえば、「ただツールやライブラリの使い方を紹介するだけで有名人になっている人達がいる」ように見えてきたのです。

Webカンファレンスのほとんどの発表内容は、ツールやライブラリの構造上の理解や仕組み、またはそれによる営利的なメリットではなく、「このツールを使えばこんなことができる」という使い方の説明であり、多少の業務経験がある人が英語リファレンスを読み解けば、比較的容易に発表できてしまう内容が多かったです。

さらに業界を観察していると「新しい技術を紹介したもの勝ち」的な雰囲気が蔓延しており
目的を見失ってそのスピードの速さ、紹介するレトリックの巧みさが競われているような文化さえ感じたのです。

そして周囲から有名になると、まるで芸能人のように祭り上げられます。(それが業界の面白さや楽しさでもあるのは理解してます)

世の中に何かをアウトプットできる
インフルエンサーとしての能力を持っている

これは素晴らしいことですし、その能力を持っていない人より持っている人は優れていると思います。
しかしそれがイコールその人の技術者としての技術力ではないはずです。

ツールやライブラリをスクラッチで自作した本人じゃなければ評価されちゃいけないと言ってるわけではないです。

ですが、Web業界では「ライブラリを作った人」より「上手く紹介した人」の方が有名になっている、といった違和感を感じました。

技術とその価値は「具体的な成果物を出して世の中にどれだけの影響を与えたか?」ではないでしょうか?

その成果物は技術の紹介ブログ記事やTwitterやカンファレンスの発表やQiitaのGood数やGitHubのスターの数ではなく、「直接、あるいは間接にでも売上に貢献した目に見えるプロダクト」であるべきです。

極端かもしれませんが、これが基準にならなければ評価の基準も曖昧になります。

最後に、私はWeb業界の雰囲気が嫌いではありません。
Qiitaを見ていると新しいことを学ぶモチベーションが高まります。

ですが、Qiitaやブログの記事を一つ読むにしても、その人は「本当に価値のあるアウトプット」をしている人なのかを考えながら接する必要があると思っています。

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

年末まで毎日webサイトを作り続ける大学生 〜41日目 要素が画面内に入るとふわっとフェードインさせる〜

はじめに

こんにちは!@70days_jsです。

要素が画面内に入るとふわっとフェードインする機能を作りました。

今日は41日目。(2019/11/28)
よろしくお願いします。

サイトURL

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

やったこと

画面内に入るとふわっとフェードインするようにしました。
test2.gif

今回はJavaScriptとCSSを併用するので、
まずJavaScriptで要素が画面内に入るのを検知する機能を作りました。
そのあとCSSでふわっとする機能をつけました。

ではJavaScriptの方から説明していきます。

要素が画面内に入ったのを検知する(JavaScript)

まず考え方ですが、要素が画面内に入ったのを検知する機能というのは、「要素が任意の位置までくれば検知する」機能を作ればカバーできます。

というわけで、とりあえず画面の半分を越えれば要素の色が変わるコードを書きました。

gif↓
test1.gif

画面の半分を越えれば青色が赤色に変わっているのが分かると思います。

ではhtmlからいきます。

html
<body>
    <div class="blank">空白</div>
    <div class="fade-out">
        test
    </div>
    <div class="blank">空白</div>
    <div class="fade-out">
        test2
    </div>
    <div class="blank">空白</div>
</body>

.fade-outクラスのついたdivの背景色を変えていました。
次はcss↓

css
body {
    margin: 0;
}

.blank {
    height: 200vh;
    background-color: yellow;
}

.fade-out {
    background-color: blue;
}

.fade-in {
    background-color: red;
}

画面の半分を越えれば.fade-inクラスをつける事で、背景色が青色から赤色に変わります。
次はJavaScript↓


##メモ
//色を変えたい要素(.fade-outクラスのついた要素)を取得する変数
let contents = document.querySelectorAll('.fade-out');
//画面の高さを取得する変数
let windowIn = window.innerHeight;
//現在位置を格納する変数
let nowPosition;
//付け加えるクラス名を格納する変数
let myClassName = 'fade-in';

//querySelectorAllは配列を返すので、for文で要素を1つずつ取り出します
for (var i = 0; contents.length > i; i++) {
//取り出した要素を格納します
    let content = contents[i];
//取り出した要素の位置を取得します
    let contentPosition = content.getBoundingClientRect().top;

//スクロールが行われると関数を実行するイベントリスナーをつけます
    window.addEventListener('scroll', function () {
//現在位置を取得します
        nowPosition = window.pageYOffset;
//要素に.fade-inクラスがなければ実行します。あれば実行しません(これはやる必要ないかも)
       if (content.classList.contains(myClassName) === false) {
//画面の高さ + 現在位置 が、要素の位置+画面の半分を越えれば.fade-inクラスをつけて色を変更します
            if (windowIn + nowPosition > contentPosition + (windowIn / 2)) {
                content.classList.add(myClassName);
            }
        }
    }, false)
}

大体のことはコメントに書いておきました。

最後の方にある(windowIn / 2)が画面の半分を表しています。
この部分を好きな位置に変更すれば、好きな位置で要素に何かしらの変化を与えることができるようになります

ふわっと表示する機能をつける(CSS)

transformとtransitionを使って実装します。
今回は海外のウェブサイトでよくみる「要素が画面内に入ったらふわっとフェードインするあれ」の実装方法 | imasashi.net さんのサイトで紹介されていた書き方を使います。↓

.fade-out {
    opacity : 0.1;
    transform : translate(0, 50px);
    transition : all 500ms;
}

.fade-in {
    opacity : 1;
    transform : translate(0, 0);
}

opacityで最初は薄い表示にしていますね。
特定の位置までくればopacityを1にして、translateを0にして、はっきりと元の位置で要素を表示するようになっていますね。

組み合わせて、画面内に入るとふわっとフェードインさせる機能を作る

あとは上でやったことを組み合わせるだけでOKです。

body>
    <div class="blank">
        <h1>Awesome Site</h1>
        <p>fugafuga</p>
    </div>
    <div class="box-wrapper">
        <div class="box fade-out">box1</div>
        <div class="box fade-out">box2</div>
        <div class="box fade-out">box3</div>
        <div class="box fade-out">box4</div>
        <div class="box fade-out">box5</div>
        <div class="box fade-out">box6</div>
        <div class="box fade-out">box7</div>
        <div class="box fade-out">box8</div>
        <div class="box fade-out">box9</div>
        <div class="box fade-out">box10</div>
    </div>
    <div class="blank">空白</div>
</body>

フェードインするには長さが必要なので、場所取りのために.blankクラスのついたdivを書いています。

cssは何も変わりません。↓

.fade-out {
    opacity : 0.1;
    transform : translate(0, 50px);
    transition : all 500ms;
}

.fade-in {
    opacity : 1;
    transform : translate(0, 0);
}

次はJavaScriptですが、プラスアルファで何かしたくて、要素の背景をランダムで設定する機能をつけたりしました。↓

let contents = document.querySelectorAll('.fade-out');
let windowIn = window.innerHeight;
console.log('windowの高さ: ' + windowIn);
let nowPosition;
let myClassName = 'fade-in';


for (var i = 0; contents.length > i; i++) {
    let content = contents[i];
    let contentPosition = content.getBoundingClientRect().top;
    console.log('contentの位置: ' + contentPosition);

    randomColor(content);

    window.addEventListener('scroll', function () {
        nowPosition = window.pageYOffset;
        console.log('現在位置: ' + nowPosition);
        if (content.classList.contains(myClassName) === false) {
            if (windowIn + nowPosition > contentPosition + (windowIn / 4)) {
                content.classList.add(myClassName);
            }
        }
    }, false)
}


function randomRGB() {
    let r = Math.floor(Math.random() * 256);
    let g = Math.floor(Math.random() * 256);
    let b = Math.floor(Math.random() * 256);
    let rgb = [r, g, b];
    return rgb;
}

function randomColor(element) {
    let rgb = randomRGB();
    let r = rgb[0];
    let g = rgb[1];
    let b = rgb[2];
    element.style.backgroundColor = 'rgba(' + r + ', ' + g + ',' + b + ', .5)';
}

要素がフェードインする場所も、(windowIn / 4)で1/4に変えています。
ここはロジックとかではなく、試してみていい感じのところにしました。

感想

サイトに動きがあると華がでるというか、なんかいいですね。。。

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

参考

  1. 海外のウェブサイトでよくみる「要素が画面内に入ったらふわっとフェードインするあれ」の実装方法 | imasashi.net (https://imasashi.net/element-fadein.html)

とても分かりやすかったです。ありがとうございます!

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

JavaScriptで15分毎にデスクトップ通知するアラームを作る(通知APIとクロージャ)

やりたいこと

個人での長時間のPC作業中に、振り返ったり頭を切り替えたりできるようにするため、15分くらいの間隔でお知らせしてくれるアラームがほしい。

方法の検討

  • スマホのアプリだとPC作業に気づかなかったり、逆に集中が切れてしまったりするので、PCでこっそり動くのがいい
  • でも作業用PCに余計なアプリは入れたくない

自分でJavaScriptで作って、ブラウザで開いておく方法を試してみる。

通知APIを動かす

JavaScriptを使うにしても、余計なライブラリはなるべく使わずに済ませたい。
幸い標準で通知APIなるものが用意されてるので、これを使う。
まずは単に通知を表示するスクリプトを作ってみる。

notification.js
window.onload = function() {
   Notification.requestPermission();
   const notification = new Notification("Check!");
};

↑を読み込む適当なHTMLを作って、ブラウザで開く。

alarm15.html
<html>
<head>
   <script src="notification.js"></script>
</head>
<body>
</body>
</html>

HTMLを表示すると、よくある感じの通知の許可要求ダイアログが表示される。
(このへんWebに公開する場合はいろいろ考えなきゃいかんけど、ローカルなのであまり気にせず)
image.png
許可すると、画面右上によくある感じの通知が表示される。
image.png
これを15分ごとに表示させるようにしたい。

実装その1 シンプルに

まずは素直に実装してみる。

alarm15-simple.js
window.onload = function() {
   Notification.requestPermission();
   setInterval(checkTime, 1000);   
};

let previousMinutes;

const checkTime = function() {
    const currentTime = new Date();
    const minutes = currentTime.getMinutes();
    if (previousMinutes !== minutes && minutes % 15 === 0) {
        previousMinutes = minutes;
        const notification = new Notification("Check!");
    }
};

setInterval(checkTime, 1000) と書くことで、1秒間隔で関数 checkTime を実行する。
関数 checkTime では現在時刻をチェックして、0/15/30/45分だったら一度だけデスクトップ通知をする。
(通知の間隔を変えたければ minutes % 15 の数値を変えればよい。最初は1分間隔で試すとよい)

これでやりたいことは実現できたけど…
このコードだと、直前の「分」を保持している変数 previousMinutes をグローバルスコープに置いてしまっている。
これを安全なスコープに閉じ込めてあげたい。

実装その2 クロージャ

このようにしてスコープを閉じられた。見た目の動作は同じ。

alarm15-closure.js
window.onload = function() {
   Notification.requestPermission();
   setInterval(checkTime, 1000);   
};

const checkTime = function() {
   let previousMinutes;
   return function() {
       const currentTime = new Date();
       const minutes = currentTime.getMinutes();
       if (previousMinutes !== minutes && minutes % 15 === 0) {
           previousMinutes = minutes;
           const notification = new Notification("Check!");
       }
   }  
}();

いい感じのクロージャのサンプルになったので、ざっくり説明。

定数 checkTime には、無名関数を即時実行した結果が代入される。
無名関数を即時実行して返ってくる結果は return function() ... と記述のある通り、これまた無名関数。
この無名関数のコードは実装その1の checkTime 関数とまったく同じ。
結果として、実装その1とその2はまったく同じ関数 checkTime を実行するように見えるけど…。

実装その1とその2で、関数 checkTime の違いは、関数のスコープ。
実装その1は、最も外側に関数が定義されているので、関数 checkTime のスコープはグローバルスコープ。
実装その2は、外側の無名関数内に関数が定義されているので、関数 checkTime のスコープは外側の無名関数が作るローカルスコープ。
(このように、実行時でなく定義時に決まるスコープをレキシカルスコープと言う)

で、実装その2は同じローカルスコープに変数 previousMinutes が定義されている。
同じスコープなので関数 checkTime からアクセスできるけど、他のスコープにある関数からはアクセスできない。
これで、変数 previousMinutes は必要な関数からのみアクセスされる、安全なスコープに閉じ込めることができた。

おまけ設定

通知が味気ないので、ちょっとだけオプションを設定してみる。

alarm15-closure.js
window.onload = function() {
   Notification.requestPermission();
   setInterval(checkTime, 1000);   
};

const checkTime = function() {
   let previousMinutes;
   const options = {
       body: "調子はどうだい?",
       icon: "img/youkai_kappa.png"
   };
   return function() {
       const currentTime = new Date();
       const minutes = currentTime.getMinutes();
       if (previousMinutes !== minutes && minutes % 15 === 0) {
           previousMinutes = minutes;
           const notification = new Notification("Check!", options);
       }
   }  
}();

こんなかんじになる。
(画像はお好みで)
image.png

参考資料

Notifications API を使用する - WebAPI | MDN
WindowOrWorkerGlobalScope.setInterval() - Web API | MDN
開眼! JavaScript 第7章「スコープとクロージャ」
JavaScript の原理:クロージャの真実

おわりに

この記事は Mikatus Advent Calendar 2019 7日目として公開しました。
明日は我らがリーダー @takuya0301 さんです!
参考資料の「クロージャの真実」もぜひご覧ください(本記事の説明はざっくりなので)。

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

jQueryでシンプルなSPAのサンプルを作ってみた

jQueryでシンプルなSPAを実装してみた

SPAを本格的に実装するのであればReactやVueを実装する方が良いのだろうけど、簡単なSPAであればjQueryでも実装は可能です。
完成イメージは下記のようになり、添付画像の黄色い箇所だけ切り替わります。
spa2.png

HTML

spaクラスがついているdivタグをjQueryで上書きします。

HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>シンプルSPA</title>
<link rel="stylesheet" type="text/css" href="assets/reset.css">
<link rel="stylesheet" type="text/css" href="assets/spa.css">
</head>
<body>
<div class="wrapper">
  <header class="header">
    <nav>
      <a href="#page1">page1</a> | 
      <a href="#page2">page2</a> | 
      <a href="#page3">page3</a>
    </nav>
  </header>
  <main id="spa">
    <!-- ここが切り替わる -->
    <div class="spa"></div>
  </main>
  <footer class="footer">
    footer
  </footer>
</div>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="assets/spa.js"></script>
</body>
</html>

ハッシュに対応したファイル名のHTMLを用意しておきます。
例:ハッシュが#page1の場合、page1.htmlを読み込むようにします。
page2.html、page3.htmlも同様です。

page1.html
<div class="page1 page">page1</div>

CSS

説明は割愛。

CSS
@charset "utf-8";

.wrapper {
    width: 600px;
    margin: auto;
}
.page {
    text-align: center;
    line-height: 300px;
    font-size: 24px;
    font-weight: bold;
}
.page1 {
    background: #f0e68c;
}
.page2 {
    background: #4169e1;
}
.page3 {
    background: #ffb6c1;
}
.header,
.footer {
    text-align: center;
    background: #ccc;
    padding: 20px;
}

JavaScript

hashchangeイベントを使用して、URLのハッシュが切り替わったら読み込むHTMLファイルを切り替えさせています。
イレギュラーなハッシュを叩かれた場合や初回アクセス時はinit()の方でpage1.htmlを読み込ませます。

参考:MDN WindowEventHandlers.onhashchange

JavaScript
;(function(){
'use strict';

//URLの一覧を配列で用意
const url_list = [
  'page1',
  'page2',
  'page3',
];

function init(){
  $.get('/page/page1.html').done((data) => {
      $('.spa').html(data);
  }).fail(() => {
      error();
  });
}

function hashchange(){
  const page = location.hash.slice(1);
  const in_url = $.inArray(page, url_list);
  if(in_url !== -1){
    $.get(`/page/${page}.html`).done((data) => {
      $('.spa').html(data);
    }).fail(() => {
      error();
    });
  }
}

function error(){
  $('.spa').html('読み込みエラー');
}

$(window).on("hashchange", () => {
  hashchange();
});

$(function(){
  init();
});

})();

まとめ

URLの階層が深くなった場合や動的URLに対応していない最低限の実装ですがSPAのようなものが実装できました。
本格的に実装するのであれば大人しくVueなどのフレームワークを使用した方がバグも少なく実装できるかと思いますが、既存サイトの一部にちょっと実装する場合や個人サイト程度ならこのくらいでも十分かと思います。

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

v-ifで同一のコンポーネントの表示を切り替えた時にcreatedが発火しない問題とその解決

たぶんすごい基礎中の基礎なんだろうけど、結構詰まったので。

どういうことか

例えばこんなコンポーネントがあるとする。
渡したvalueを表示するだけのコンポーネント

TestComponent.vue
<template>
  <div>
    {{ value }}
  </div>
</template>

<script>
export default {
  props: {
    value: { type: String }
  },
  created() {
    console.log("created");
  }
};
</script>

こんな感じで利用する

Main.vue
<template>
  <div>
    <button @click="click">Click</button>
    <div>
      <testComponent value="a" v-if="testOpen"/>
      <testComponent value="b" v-else/>
    </div>
  </div>
</template>

<script>
import testCompoment from "TestComponent"

export default {
  data() {
    return {
      testOpen: false
    }
  },
  components: {
    testComponent
  },
  methods: {
    click() {
      this.testOpen = !this.testOpen;
    }
  }
};
</script>

クリックすると片方のコンポーネントが表示され、もう片方が消え、画面表示のa,bが切り替わる画面

予測していた動作

クリックをするたびにcreatedが呼ばれ、画面表示のa,bが切り替わる。

実際の動作

createdが最初しか呼ばれない。

See the Pen wvvVJXj by 7tsuno (@7tsuno) on CodePen.

なぜか

Vue は要素を可能な限り効率的に描画しようとする。
ここでは二つのtestComponent要素が同じものだとされ、初期化処理が走らないようになっている。

解決策

コンポーネントに別々のkeyをつける。
keyをつけることでvueに"この2つの要素は完全に別個のもので、再利用しないでください"と伝えることが出来る。

Main.vue
<template>
  <div>
    <button @click="click">Click</button>
    <div>
      <testComponent value="a" v-if="testOpen" key="testA"/>
      <testComponent value="b" v-else key="testB"/>
    </div>
  </div>
</template>

<script>
import testCompoment from "TestComponent"

export default {
  data() {
    return {
      testOpen: false
    }
  },
  components: {
    testComponent
  },
  methods: {
    click() {
      this.testOpen = !this.testOpen;
    }
  }
};
</script>

参考

公式 : 条件付きレンダリング

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

A-FRAME: 物理演算でボーリングっぽい動きを実現してみる7(ピンのモデル)

A-Frameをつかって物理演算ができるようにしてみます。
前回、ピンの物理的な形状を設定し、sphereとcylinderにおいて中心の不一致が見られました。

今回は利用するモデルによって差があるのかどうかを確認してみます。

例1)pin1

以前から利用しているモデルです。
pin1.gifdemo
中心がずれてます。

例2)pin2

これまでとは別のモデルを試してみます。
pin2.gifdemo
これもずれてます。

例3)pin3

さらに別のモデルです。
pin3.gifdemo
ちょっと違うずれ方ですが、ずれてます。

例4)pin4

さらに別。
pin4.gifdemo
ずれてる。

まとめ

4つのボーリングのピンのモデルを試してみました。
モデルにより物理的な形状とレンダリングされる形状の中心のずれ方は違いましたが、一致もしませんでした。

blender等を用いてモデル側で調整ができるかもしれないですが、
どうせモデルによってズレるのであればA-Frame側で調整する方が良いような気もします。

次はA-Frame側で物理的な形状とレンダリングされる形状の中心を揃えてみたいと思います。

参考

今回利用させていただいたピンのモデルです。
- pin1 - Bowling Pin, MSerdar Tekin
- pin2 - Bowling pin, Vas3D
- pin3 - Bowling Pin, Hassan Sheikh
- pin4 - Pin, Adam.Klimo

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

ZEIT Nowで静的サイトの速度を15倍改善した話

はじめに

公開されているコンテナの脆弱性をまとめるサイトを運用しています。
image
https://containers.goodwith.tech/

サイトのホストには、当初Netlifyを利用していて、ファイルサイズの大きいデータがある場合、データのダウンロードにとても時間がかかっていたのですが、ためしにZEIT Nowを利用したところ、劇的に速度が改善しました。

その後、それぞれのサービスの違いなどをまとめたものを記事にします。

結論

  • 大きなファイルを扱う場合はZEIT Nowが圧倒的に速い
    • ZEIT NowはBroti圧縮なので多少ファイルサイズが小さくなる
    • ZEIT NowのWTTFが長いので何かしら最適化処理を行っているのかも
  • 最終的には、どちらも利用して比較してみるのがいいと思う

HTTP responseで見た比較表

ざっくりした違いをまとめました。

ZEIT Now netlify
URL https://vulnimage-4wgya6bpg.now.sh/ https://containers-goodwithtech.netlify.com/
protocol HTTP/2 HTTP/2
圧縮形式 Broti gzip
証明書 Let's Encrypt Authority X3 DigiCert SHA2 Secure Server CA
WTTF (10MBダウンロード時) 1.2~2.0sec 100~500msec
Download (10MBダウンロード時) 100~500msec 20~30sec
MAX_CONCURRENT_STREAMS
(並列ダウンロード/ストリーム)
128 150

雑な見解を言うと、ZEIT Nowの方が新しい技術を採用しています。

MAX_CONCURRENT_STREAMSに関しては Google Developerの記事が参考になります。複数のファイルを読み込む場合、基本的には数が大きい方が有利です。

詳細

Netlify

curl

    $ curl -I https://containers-goodwithtech.netlify.com/jsons/vaikas:nodejsservice/trivy.json 
    HTTP/2 200
    accept-ranges: bytes
    cache-control: public, max-age=0, must-revalidate
    content-length: 10456206
    content-type: application/json
    date: Sat, 02 Nov 2019 18:47:03 GMT
    etag: "9a7a82ab949f5eca3a33a545963f047f-ssl"
    age: 1
    server: Netlify
    x-nf-request-id: aa867750-0a7d-46af-a13f-4b6f09248853-28026257

Lighthouse

10MBのデータをダウンロード時のタイムライン
10MBのデータをダウンロード時

now.sh

curl

    $ curl -I https://vulnimage.tomoyamachi.now.sh/jsons/vaikas:nodejsservice/trivy.json 
    HTTP/2 200
    date: Sat, 02 Nov 2019 18:35:59 GMT
    content-type: application/json; charset=utf-8
    cache-control: public, max-age=0, must-revalidate
    content-length: 10456206
    content-disposition: inline; filename="trivy.json"
    access-control-allow-origin: *
    accept-ranges: bytes
    etag: W/"73c2159a3893c7ec49a81685696797519f7d0e4342cf77c51eed18905a1b28f1"
    x-now-cache: MISS
    x-now-trace: hnd1
    server: now
    x-now-id: hnd1:mzb2v-1572719759100-c8367fe1ed25
    strict-transport-security: max-age=63072000

Lighthouse

10MBのデータをダウンロード時のタイムライン
10MBのデータをダウンロード時

まとめ

両方、めちゃくちゃよくできたサービスで、基本無料で利用できるのに選択肢があるとか、めちゃくちゃ贅沢ですね。
今後もまだ使っていないサービスがあったら積極的に試してみて、他サービスと比べてみようと思いました。

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

UIをきちんと考えた実装 - モーダルダイアログ (モーダルウィンドウ)

Webサイトやアプリなどのソフトウェア開発において、ユーザーのアクション・操作に対して何らかのレスポンスを返す、という相互的なUIを実装する機会があります。

こうしたUIに対し見た目先行で仕組み・機能への理解が足りないのか、疑問符のつく使われ方を見ることが割とあります。デザイナーであっても開発実装者であっても、そのUIの原型がどういった目的や仕様で生み出されたものなのかを調べるなどして、適用前に一度問い直してみることは大事かなと思います。

モーダルダイアログ (モーダルウィンドウ)とは?

なかでもよく誤用されるパーツの例として、今回は「モーダルダイアログ(モーダルウィンドウ)」を取り上げます。以下が大まかなモーダルダイアログの基本仕様です。

  1. アクションを実行すると画面最前面にウィンドウが現れ、選択肢を選ぶ、文字入力する、閉じるなど何かの操作をユーザーにさせる
  2. 別ウィンドウが表示され操作が終わるまでの間、操作の起点となった画面(背景または親画面)は操作を受け付けない

「モーダルウィンドウ」での検索ボリュームのほうが大きいのですが「モーダルダイアログ」名称のほうがこのUIの機能や役割がよりつかみやすいため、本記事ではモーダルダイアログとします。
モーダルダイアログUIのサンプル画面キャプチャ。ダイアログを開いたところ
モーダルダイアログUIのサンプル画像

名称の定義

「モーダル」が正式名称かのような場面に多く出会います。モーダル(modal)とは英語で「形式上の~」、コンピューター用語としては「モードを持つ~」といった意味の形容詞で、モノ(名詞)のように呼ぶのは略称だとしても変です。

「ダイアログ」または「ダイアログボックス」とするのが適切でしょう。ダイアログ(dialog)は「対話」(名詞)を意味し、システムとユーザーによる対話形式のUIととらえるとしっくり来ます。

「ポップアップ(pop-up)」や「ライトボックス(Lightbox)」と呼ぶケースにも遭遇したことがあるのですが、それらよりもダイアログの名称がふさわしいでしょう。

OOUIにおけるモーダル、モードレス

せっかくモーダルという単語が出ているので、ソフトウェア開発において重要なOOUI(オブジェクト指向ユーザーインターフェース/オブジェクトベースUIとも)の考え方と併せて、このUIの定義を考えていきます。

コンピューターアプリケーションのインターフェース設計において、モードレス(modeless)モーダル(modal)という2つの対比的な思想が存在します。以下、その意味についてソシオメディアの用語集から引用します。

モードレス
modeless
モードがない状態。ユーザーインターフェースをデザインする際に目指すべき状態。状況に依存した機能制限がなく、自由な手順でタスクを進行することができ、かつ特定の操作がシステムによって常に一定に解釈される状態。

モーダル
modal
モードがある状態。つまり、システムが特定の機能の使用に制限された状態。ユーザーが自由に操作を行えなくなることと、モード別に機能の意味や振る舞いが変化することから、ユーザーインターフェースのデザインでは、できる限りモードを設けないほうがよいとされる。

引用元:ソシオメディア | モードレスソシオメディア | モーダル

モードレスとは、コンピューターまたはWebサイト・アプリを操作する際に操作を限定されない自由な状態を指し、モーダルはその逆でシステム側から操作を限定された状態を指します。

まずユーザーが目的(オブジェクト)を選び、次に自由な操作・アクションが選択できることがGUI(グラフィカル・ユーザー・インターフェース)の基本思想であり、ソフトウェアデザインの出発点です。このオブジェクトベースなUIにおいて、モーダルは極力避けるべき状態とされています。

私たちはGUIユーザーとして、ときどき運営者都合で操作を制限された状態に遭遇します。それはビジネス側・売る側のロジックという強制力が働いた、不自由で押しつけがましいものになってはいないでしょうか。
とあるWebサイトのトップ画面。表示と同時にダイアログが開かれる
画面を開いた際に即出現して「ぐぬぅ...」となるモーダルダイアログ例。ユーザーの行動の選択肢を狭めストレスを感じてしまう。disってないよ

モードレスダイアログという選択肢、あるいは...

モーダルダイアログがこの形態のUIの通名になっている感がありますが、モードレスダイアログも当然存在します。ダイアログがモーダル(モードが存在して制限されている)か、モードレス(モードが存在せず自由度が高い)かという状態の両軸を表しているだけです。

そして、よほどユーザーの操作を限定する必要がなければモーダルではなく、モードレスダイアログを検討・選択しても良いのかもしれません。ただでさえ不意に画面を遮って現れるのです。操作の自由はなるべくユーザーに委ねるほうがストレスにもつながらないでしょう。

モーダル(操作を絞る)がユーザーにとって有効なケース(必須の確認事項や限定されたプロセスなど)も確かに存在するので、使い分けやそもそもダイアログを利用しないことを慎重かつ適切に判断していきたいところです。

Microsoft Office ExcelのUI画面。「検索と置換」ダイアログボックスを開いているところ
モードレスダイアログの例
Excelの「検索と置換」ダイアログは開いていても、背景画面の操作が可能。同様の機能はWebでも見ます

機能定義と実装

それではここまでの流れを踏まえてモーダルダイアログの機能要件を定義し、Web UIとしての実装に入っていきたいと思います。

機能定義

  • ボタンアクションで画面最前面にダイアログボックス(疑似的な別ウィンドウ)が現れる
  • ダイアログが開いたら自身にフォーカスし、次のタブ操作でダイアログ内1番目のインタラクティブ要素にフォーカスする
  • ダイアログ内では選択肢を選ばせるなどの限定的な操作にする
  • ダイアログ表示中は、起点となる画面(背景または親画面)に対する操作やフォーカスを受け付けない 
  • ダイアログを閉じるボタンを設定する
  • ダイアログ外をクリック・タッチした場合、ダイアログを閉じる 
  • [Esc](Escape)ボタン押下でダイアログを閉じる
  • ユーザーが操作を終えたらダイアログを閉じ、何らかの値を返す

マークのついた項目は、モードレスダイアログである場合は不要な機能を表します

実装方針

  • HTMLのdialog要素(open属性)とJavaScript APIのHTMLDialogElementを用いて実装する
  • 上記の技術は現状一部の主要ブラウザ(Chrome、Edge)しか実装対応していないため、Polyfill(dialog-polyfill)を組み込んで対応外ブラウザ(Firefox、Safari)にも対応する
  • ダイアログが開いた際の背景または親画面は、明度を下げ操作不能を示す。暗い背景レイヤーはCSS ::backdrop疑似要素で実装

※ サンプルコードでは、role属性やaria-*属性を用い支援技術による適切な情報伝達を意識しています。キーボード操作などで必要なフォーカス管理をJSで補い、CSSに構造依存しないUI実装を心がけています

コード

HTML・head内
<script src="https://cdnjs.cloudflare.com/ajax/libs/dialog-polyfill/0.5.0/dialog-polyfill.min.js" defer></script>
<script src="main.js" defer></script>
HTML・body内
<div id="modal-dialog" class="modal-dialog">
  <h1>モーダルダイアログ</h1>
  <button type="button" id="dialog-open" class="dialog-open">
    モーダルダイアログ<br>
    (モーダルウィンドウ)を開く
  </button>
  <dialog id="dialog-panel" class="dialog-panel" role="dialog" aria-describedby="d-message">
    <p id="d-message" class="dialog-panel__message" role="document">
      モーダルダイアログ(モーダルウィンドウ)<br>
      とはどんなものか知っていますか?
    </p>
    <div class="dialog-panel__buttons">
      <button type="button" id="dialog-yes" class="dialog__button">はい</button>
      <button type="button" id="dialog-no" class="dialog__button">いいえ</button>
    </div>
    <button type="button" id="dialog-close" class="dialog__close" aria-label="このモーダルダイアログを閉じる">
      ×
    </button>
  </dialog>
  <p class="dialog-response">
    ダイアログの返り値:<span id="return-value"></span>
  </p>
</div>
JavaScript
class ModalDialog {
  constructor() {
    this.wrap = document.getElementById('modal-dialog');
    this.open = document.getElementById('dialog-open');
    this.dialog = document.querySelector("[role='dialog']");
    this.yes = document.getElementById('dialog-yes');
    this.no = document.getElementById('dialog-no');
    this.close = document.getElementById('dialog-close');
    this.returnSpan = document.getElementById('return-value');

    // Polyfillを読み込む関数
    dialogPolyfill.registerDialog(this.dialog);

    this.showDialog();
    this.hideDialog();
    this.respondValue();
  }
  showDialog() {
    this.open.addEventListener('click', () => {
      this.dialog.showModal();
      this.dialog.style.visibility = 'visible';
      this.dialog.classList.remove('is-motioned');
      this.dialog.setAttribute('tabindex', '0');
      this.dialog.focus();
    });
  }
  hideDialog() {
    this.yes.addEventListener('click', () => {
      this.hideProcess('はい');
    });
    this.no.addEventListener('click', () => {
      this.hideProcess('いいえ');
    });
    this.close.addEventListener('click', () => {
      this.hideProcess('きみ、閉じるボタンを押したね...');
    });
    this.dialog.addEventListener('cancel', () => {
      this.hideProcess('Escapeボタン押しました?');
    });
    this.dialog.addEventListener('click', (event) => {
      if (event.target === this.dialog) {
        this.hideProcess('きみ、ウィンドウの外を押したね...');
      }
    });
  }
  hideProcess(resText) {
    this.dialog.close(resText);
    this.dialog.classList.add('is-motioned');
    this.wrap.setAttribute('tabindex', '0');
    this.wrap.focus();
    setTimeout(() => {
      this.dialog.style.visibility = 'hidden';
    }, 250);
  }
  respondValue() {
    this.dialog.addEventListener('close', () => {
      this.returnSpan.innerHTML = this.dialog.returnValue;
    });
  }
}

const modalDialog = new ModalDialog();
CSS
/* 最低限のスタイル */
.modal-dialog {
  width: 100%;
  max-width: 64.25rem;
  margin: 2rem auto;
}

.dialog-open {
  display: block;
  width: 300px;
  margin: 4rem auto;
  padding: 1rem;
  border: 1px solid hsla(0, 0, 40%, 1);
  text-align: center;
  border-radius: 5px;
}

.dialog-panel {
  position: absolute;
  width: -moz-fit-content;
  width: -webkit-fit-content;
  width: fit-content;
  height: -moz-fit-content;
  height: -webkit-fit-content;
  height: fit-content;
  top: 15%;
  left: 0;
  right: 0;
  margin: auto;
  padding: 0;
  border: 2px solid hsla(0, 0, 40%, 1);
  background: hsla(0, 0, 100%, 1);
  border-radius: 5px;
}

/* native backdrop */
.dialog-panel::backdrop {
  background-color: rgba(0, 0, 0, .5);
}

/* polyfill backdrop */
.dialog-panel + .backdrop {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: rgba(0, 0, 0, .5);
}

.dialog-panel__message {
  margin: auto;
  padding: 4rem 3rem 0;
}

.dialog-panel__buttons {
  text-align: center;
  padding: 2rem 0 2.5rem;
}

.dialog__button {
  display: inline-block;
  min-width: 5rem;
  padding-top: .625rem;
  padding-bottom: .525rem;
  border: 1px solid hsla(0, 0, 40%, 1);
  border-radius: 5px;
  text-align: center;
  line-height: 1.5;
}

.dialog__button:not(:first-of-type) {
  margin-left: 1.75rem;
}

.dialog__close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  width: 2.3rem;
  height: 2.3rem;
  background-color: hsla(0, 0, 20%, 1);
  font-size: 2rem;
  color: hsla(0, 0, 100%, 1);
  border-radius: 50%;
}

HTMLDialogElementWeb APIのshow() showModal() close()メソッドやプロパティなどのコードや仕様についても細かく解説したかったのですが、それ以外の説明がやや長くなってしまい、力尽きたのでここで終わります。

実装の詳細については上記コードと下記参考URLをご参照ください。

参考URL

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