- 投稿日:2019-08-29T23:44:20+09:00
初めてのvue.js 簡易的なメモ・備忘録編
はじめに
まず、私の説明だけ簡単にさせていただきます。
とあるベンチャー企業でフロントエンドエンジニアとして働いています。
ライブラリーはReactを使い、1年にも満たないですが、redux,redux-saga,非同期通信などなど、様々なことに挑戦して、Reactを自信を持って書けるようになりました。
そんな私が、あるきっかけでvue.jsを使用することになってので、勉強録としてまとめてみました。今回は、まとめというより、メモという意味合いが強いです。
vue.jsやフロントエンドに強い方、謝りや誤認識等ありましたら、ぜひご指摘ください!こちらの書籍をベースに学習しております。
Reactに馴染んでいる私にとって、非常にわかりやすく、スラスラ読める一冊です!
初めてプログラミングに触れた時は、全く理解できなかったが………
Vue.js入門 基礎から実践アプリケーション開発までそれでは。
オプション(メソッドのようなもの)
data
UIの状態・データ
Reactでいうstateみたいなものかな?変数のような概念かな?
このdata
を通して、html側にデータを送るようです。
名前から役割が想像しやすいですね。el
vueインスタンスをマウントする要素。
マウントがよく分かっていません。
el
でマウントすることで、該当htmlにデータを送受信したり、計算したり、加工したりできるようになる。メソッドによるマウントもでき、その場合は$mountを使うようです。
filters
データを文字列と整形する。
データを 計算 するのではなく、あくまでも整形する。
後述ののcomputed
と役割が被って見えます。methods
イベントが発生した時などの振る舞い。
onClick等の処理を書いているところですね。computed
データから派生して算出される値。
要は、受け取ったデータを 計算 しているものですね。
前述のfilters
と役割が被って見えます。テンプレート構文
{{}}
Mustache構文(二重中括弧)という名前らしいです。ディレクティブ
オプションと同じように、vue.jsで重要な概念の一つ。
ディレクティブにより、onClickで処理をメソッドに渡したり、繰り返しの構文を使えたり、formのデータを取得できるようです。v-bind
`v-bind: 属性名 = "データを展開した属性値"
buttonのdisableやstyleなどに、vueのデータやメソッドを挟み込めるようになりますね。省略記法として、
:
+ 属性名で記述できます。例<p v-bind:class="hoge"> ↓ <p :class="hoge">v-if/v-show
条件付きレンダリング。
名前だけやと分からないですね。
非表示・非表示を切り替える場合に使うものです。見た目上は同じような動きをしますが、v-ifはDOMの追加・削除を、v-showはdisplayプロパティの変更をするようです。
v-for
リストレンダリング。
jsのfor構文と同じく、html上でデータの繰り返し処理ができるようです。v-on
イベントハンドリング。
onClickやonChange属性にメソッドや処理を渡せるようです。省略記法として
@
があります。例<button v-on:click="hoge"> ↓ <button @click="hoge">v-model
フォーム入力バインディング。
双方向バインディングを実現できる。
Reactでは少し記述が冗長になるため、これは非常に便利!
- 投稿日:2019-08-29T21:30:36+09:00
贋作作り
明日からのWebsite作りの練習は、「贋作」作りになります。見本帳のHPを出来るだけ、似せて作ります。これからが本当の応用問題になります。私はもう資格試験は受験しません。贋作作りに明け暮れて暮らすつもりでおります。機械語とかは、勉強するかもしれませんが…。
- 投稿日:2019-08-29T20:32:47+09:00
初心者によるJavaScriptでタイピングゲームを作るための学習
この記事はドットインストール「JavaScriptでタイピングゲームを作ろう」を学習するための記事です。
今まで学んできたことを複合的に使うため、初心者にとってはなかなかやりがいのあるプログラムでした。
まだプログラムに慣れていないとこのコードはどういう意味なのかなど、引っかかったところがあったので、そのあたりを自分なりにアウトプットすることで、理解を深めたいです。まずはHTMLです。
<p id="target">word</p> <p class="info"> Letter count: <span id="score">0</span> Miss count: <span id="miss">0</span> </p>target部が打つ文字です。成功数やミスの数をカウントするので、そのエリアも用意します。
次にJavaScript。
タイピングゲームなので、
・打つべき文字列を用意し
・タイプした文字を取得し
・それを表示する
というおおまかな流れがあるのでそれを作ります。const word = 'apple'; // 打つべき文字列 const target = document.getElementById('target'); // 表示エリアを取得 target.textContent = word; // 打った文字列をセット window.addEventListener('keydown', e => { // キーを押したら以下の処理を実行 console.log(e.key); // ひとまずコンソール表示 });ざっくりこんな感じになります。
次にタイプする文字は、文字列の何番目かを管理する必要があるので、それをlocとして宣言します。
ただしこれは後に値を再代入するので、constではなくletを使います。
同様に、正解数やミス数を管理し、それを表示させるための要素を取得します。let loc = 0; let score = 0; let miss = 0; const target = document.getElementById('target'); const scoreLabel = document.getElementById('score'); const missLabel = document.getElementById('miss');正誤判定
次は正誤判定です。
打ったキー(e)がwordのloc番目の文字と同じか判定して、正解ならscoreカウントをプラス1して次の文字へ、不正解ならmissカウントをプラス1し、それらをブラウザ上に表示させます。window.addEventListener('keydown', e => { if (e.key === word[loc]) { // 打ったキー(e)がwordのloc番目の文字と同じなら loc++; // 次の文字へ score++; // 正解数プラス1 scoreLabel.textContent = score; // 正解数を表示 } else { // 違っていたら miss++; // ミス数プラス1 missLabel.textContent = miss; // ミス数を表示 } });次に打つ文字をわかりやすくする
次に行うのは、「正解した文字をなにか別のものに変えて、次に打つ文字をわかりやすくする」です。
'apple'とずっと表示されていても、どれを打つべきなのかわからないと困りますよね。2番目まで正解していたのなら'_ _ _le'となっていれば、次に打つのは'l'なんだとすぐにわかります。
従って、上記の内容を今何文字目を打つべきかはlocで管理してるので、0からloc番目までを'_'で埋めることで処理します。
そのアンダースコアを格納するための変数をplaceholderと定義して、空の文字列に初期化しておきます。そして、正解した数(locの数)の分だけアンダースコアを連結させます。それができたらブラウザにplaceholder + wordのインデックスがloc番目以降と表示させるためにtargetを更新します。
これにはsubstringを使います。
substring(引数) // 引数以降の部分文字列を取得
function updateTarget() { // 正解した文字を _ に変換させる let placeholder = ''; // '_'を格納するための空の変数 for (let i = 0; i < loc; i++) { placeholder += '_'; // 呼び出された数だけ'_'を連結する } target.textContent = placeholder + word.substring(loc); // loc番目までは'_'、loc番目以降はそのまま表示 } window.addEventListener('keydown', e => { if (e.key === word[loc]) { loc++; updateTarget(); // 上記を呼び出す score++; scoreLabel.textContent = score; } else { miss++; missLabel.textContent = miss; } });これで打つべき文字がわかりやすくなりました。
問題を複数用意し、正解したら次の問題へ移行する
問題をwordsという配列に用意し、それをwordにランダムで入れていきます。
const words = [ 'apple', 'sky', 'blue', 'middle', 'set', ]; let word = words[Math.floor(Math.random() * words.length)];次に問題の文字を全部打ち終わったら次の問題へ移行する、です。
これを実行するタイミングは、正解する(locを更新したタイミング)で、もしlocが問題の文字列数と一致したら、次のwordsに移行する、で良いでしょう。
従ってif (e.key === word[loc])
内にそれを判定するif文を追加します。window.addEventListener('keydown', e => { if (e.key === word[loc]) { loc++; if (loc === word.length) { // locが問題の文字列数と一致したら word = words[Math.floor(Math.random() * words.length)]; // 別の問題を選択する loc = 0; // locを0に初期化 } updateTarget(); score++; scoreLabel.textContent = score; } else { miss++; missLabel.textContent = miss; } });これについてMath.random()を使った際に、現在と同じものが選択されないのか?と気になったのですが、どうやらそれはないようです。なので、改めてランダムを呼び出す = 次の問題へ以降ということになります。
次の問題へ移行したら、打つべき文字は新しい問題のインデックス0番目になるので、忘れずにloc = 0;
しましょう。ゲームをスタートさせる
ページを表示したときにまず'click to start'と表示し、クリックしたらゲームが始まるようにします。
html側でhtml<p id="target">click to start</p>とし、JavaScript側でウィンドウをクリックしたら、word(問題)を表示させる処理を書きます。これもwindowに対してイベントを追加します。
JavaScriptwindow.addEventListener('click', () => { target.textContent = word; // 問題を表示 } });これで間違いなく表示されます。
タイマーを追加する
まずタイマーの初期設定です。
html<p class="info"> Letter count: <span id="score">0</span> Miss count: <span id="miss">0</span> Time left: <span id="timer">0.00</span> <!--timer表示エリア--> </p>JavaScriptlet loc = 0; let score = 0; let miss = 0; const timeLimit = 3 * 1000; // ミリ秒なので1000倍し、3秒に設定 let startTime = 0; // ゲームスタート時刻を保持するための変数 const target = document.getElementById('target'); const scoreLabel = document.getElementById('score'); const missLabel = document.getElementById('miss'); const timerLabel = document.getElementById('timer'); // タイマー要素取得次は最初にウィンドウがクリックされた時にゲームが始まるので、その時刻をstartTimeに代入し、残り時間を表示させます。その関数
updateTimer
とし、別途用意しましょう。JavaScriptwindow.addEventListener('click', () => { target.textContent = word; startTime = Date.now(); // 現在時刻を代入 updateTimer(); // 残り時間表示関数 } });ゲームの残り時間を計算する
updateTimer
関数を設定します。
残り時間はゲーム開始時刻 + 制限時間 - 現在時刻で計算できます。
例えばゲーム開始時刻:0:00:10 , 制限時間:3秒 , 現在時刻 0:00:12なら10 + 3 - 12 = 残り1秒とわかりますね。toFixed(n)は小数点以下n位まで表示させるというメソッドです。
そして上記を一定時間ごとに繰り返すことで、残り時間をカウントダウンさせます。JavaScriptfunction updateTimer() { const timeLeft = startTime + timeLimit - Date.now(); // 残り時間を計算 timerLabel.textContent = (timeLeft / 1000).toFixed(2); // タイマーラベルに秒で表示 const timeouId = setTimeout(() => { // updateTimerを呼んだ10ミリ秒後に updateTimer(); // updateTimerを呼び出す = updateTimerを繰り返す }, 10); if (timeLeft < 0) { // 残り時間が0以下になったら clearTimeout(timeoutId); // timeoutIdを解除する alert('Game Over'); // アラートを表示 } }setTimeoutとは一定時間後に処理を実行するです。この場合、10ミリ秒後に残り時間を計算し表示する、を繰り返し、残り時間が0になったら
clearTimeout
で処理を終えてアラート表示させます。タイマーの不具合を解消する
上記の場合、アラートが表示された際に残り時間が0.00から少しずれてしまうことがあります。
その対策として、ゲームが終わったらtextContentで0.00と表示させます。JavaScriptif (timeLeft < 0) { clearTimeout(timeoutId); timerLabel.textContent = '0.00'; // 0.00を表示 alert('Game Over'); }ただしこれでも正確に表示されません。その原因はブラウザによってはアラートの処理が終わるまで画面描画処理がロックされることがあるからです。要はアラート表示が早いためですね。従ってアラート表示を遅らせます。
JavaScriptif (timeLeft < 0) { clearTimeout(timeoutId); timerLabel.textContent = '0.00'; setTimeout(() => { // 100ミリ秒後にアラートを表示させる alert('Game Over'); }, 100); }こういうのは知らないと対処できない問題なので、今後のためにも対策含めてちゃんと覚えておきたいですね。
他の不具合を解消する
実行してみるとわかるのですが、
・ゲーム中クリックを連打するとその度タイマーが戻ってしまうことや、
・最後にそのクリックした数のアラートが出てしてしまうこと、
・さらにクリックしてスタートする前でもタイピング判定が行われる
などの問題が残っています。これらは、クリックしたらした分だけゲームが起動しているからなんですね。従って、ゲームを起動したらゲーム中というフラグを持たせて、多重起動しない、つまりupdateTimerが走らないようにします。
まずは最初にフラグをもたせます。当然始まっていないのでfalseですね。
JavaScriptlet loc = 0; let score = 0; let miss = 0; const timeLimit = 3 * 1000; let startTime = 0; let isPlaying = false;そしてゲームが始まったら
isPlaying
をtrueにします。ただすでにisPlaying
がtrueなら処理させないというのも必要ですね。そのあたりを条件分岐します。JavaScriptwindow.addEventListener('click', () => { if (isPlaying === true) { // isPlaying が true なら return; // 以下の処理をせずにreturn } isPlaying = true; // isPlaying を true へ target.textContent = word; startTime = Date.now(); updateTimer(); } });同様に、
isPlaying
がfalseになるのはゲームが終わった時なので、JavaScriptif (timeLeft < 0) { isPlaying = false; // ゲームが終了したので isPlaying を false へ clearTimeout(timeoutId); timerLabel.textContent = '0.00'; setTimeout(() => { // 100ミリ秒後にアラートを表示させる alert('Game Over'); }, 100); }となります。
あわせてスタート前でも判定してしまうのをどうにかするには、keydown時に
isPlaying
を確認してtrueじゃないならreturnすると付け加えてあげましょう。window.addEventListener('keydown', e => { if (isPlaying !== true) { // タイプ時に isPlaying が true じゃなかったら return する return; } if (e.key === word[loc]) { loc++; if (loc === word.length) { word = words[Math.floor(Math.random() * words.length)]; loc = 0; } updateTarget(); score++; scoreLabel.textContent = score; } else { miss++; missLabel.textContent = miss; } });こうすることで対応します。
正解率を表示する
ゲームが終わると現在はアラートでただ'Game Over'と表示されますが、これを正解率表示にします。
計算は正解数 / 正解数 + ミス数 * 100です。
ただ正解数 + ミス数が0だと計算できないので条件演算子を使っていきます。条件式 ? Trueの処理 : Falseの処理
JavaScriptfunction updateTimer() { const timeLeft = startTime + timeLimit - Date.now(); timerLabel.textContent = (timeLeft / 1000).toFixed(2); const timeoutId = setTimeout(() => { updateTimer(); }, 10); if (timeLeft < 0) { isPlaying = false; clearTimeout(timeoutId); timerLabel.textContent = '0.00'; setTimeout(() => { showResult(); // 正解率表示関数を呼び出す }, 100); } } function showResult() { const accuracy = score + miss === 0 ? 0 : score / (score + miss) * 100; // 正解率計算 alert(`${score} letters, ${miss} misses, ${accuracy.toFixed(2)}% accuracy!`); // 正解率表示 }これでうまくいきました。
リプレイができるようにする
ゲームオーバー後に'click to replay'と表示させます。
JavaScriptfunction updateTimer() { const timeLeft = startTime + timeLimit - Date.now(); timerLabel.textContent = (timeLeft / 1000).toFixed(2); const timeoutId = setTimeout(() => { updateTimer(); }, 10); if (timeLeft < 0) { isPlaying = false; clearTimeout(timeoutId); timerLabel.textContent = '0.00'; setTimeout(() => { showResult(); }, 100); target.textContent = 'click to replay'; // リプレイを促すメッセージを表示 } }もともとこれはwindowをクリックすることで、ゲームが始まるのでその段階で色々と初期化するようにすれば、リプレイにも対応できそうです。従って
window.addEventListener('click', () =>
に色々書いてしまいましょう。JavaScriptwindow.addEventListener('click', () => { if (isPlaying === true) { return; } isPlaying = true; // 以下、各項目の初期化 loc = 0; score = 0; miss = 0; scoreLabel.textContent = score; missLabel.textContent = miss; word = words[Math.floor(Math.random() * words.length)]; target.textContent = word; startTime = Date.now(); updateTimer(); } });あわせて、もともとあった初期化の項目は、宣言のみにしてあげます。
JavaScriptlet word; // 宣言のみ let loc; // 宣言のみ let score; // 宣言のみ let miss; // 宣言のみ const timeLimit = 3 * 1000; let startTime; // 宣言のみ let isPlaying = false;完成
以上ですべてのコードを書き終えました。
完成したコードは以下になります。JavaScript'use strict'; { const words = [ 'apple', 'sky', 'blue', 'middle', 'set', ]; let word; let loc; let score; let miss; const timeLimit = 3 * 1000; let startTime; let isPlaying = false; const target = document.getElementById('target'); const scoreLabel = document.getElementById('score'); const missLabel = document.getElementById('miss'); const timerLabel = document.getElementById('timer'); function updateTarget() { let placeholder = ''; for (let i = 0; i < loc; i++) { placeholder += '_'; } target.textContent = placeholder + word.substring(loc); } function updateTimer() { const timeLeft = startTime + timeLimit - Date.now(); timerLabel.textContent = (timeLeft / 1000).toFixed(2); const timeoutId = setTimeout(() => { updateTimer(); }, 10); if (timeLeft < 0) { isPlaying = false; clearTimeout(timeoutId); timerLabel.textContent = '0.00'; setTimeout(() => { showResult(); }, 100); target.textContent = 'click to replay'; } } function showResult() { const accuracy = score + miss === 0 ? 0 : score / (score + miss) * 100; alert(`${score} letters, ${miss} misses, ${accuracy.toFixed(2)}% accuracy!`); } window.addEventListener('click', () => { if (isPlaying === true) { return; } isPlaying = true; loc = 0; score = 0; miss = 0; scoreLabel.textContent = score; missLabel.textContent = miss; word = words[Math.floor(Math.random() * words.length)]; target.textContent = word; startTime = Date.now(); updateTimer(); }); window.addEventListener('keydown', e => { if (isPlaying !== true) { return; } if (e.key === word[loc]) { loc++; if (loc === word.length) { word = words[Math.floor(Math.random() * words.length)]; loc = 0; } updateTarget(); score++; scoreLabel.textContent = score; } else { miss++; missLabel.textContent = miss; } }); }かなり長い記事になってしまいました。もしかしたら分割するべきだったかもしれません。
一応編集する時はそのブロックごとにしたのですが、正直わかりにくかったと思います。
プログラムのように上に行ったり下に行ったりとする場合、どのように書いていくべきかまだつかめておりません。
行番号でも追加したほうがよかったかな。今後の課題にしたいです。
- 投稿日:2019-08-29T20:23:57+09:00
inline要素の改行のスペースが気になったので対策を調べた
これはなに
inline要素内で改行するとスペースが入りこむので、見た目的に気になったりする
https://codepen.io/aokiken/pen/vYBLMyv
例えば、以下のような、文章の途中のリンクなどに困る
<p> お問い合わせは <a href="#">こちらから</a> お願いします </p> // 出力結果 // お問い合わせは こちらから お願いしますサンプルはあくまで例だが、前後の文章やaタグにclassやattributeなど、内容によっては1行で表現しづらい
vueのカスタムディレクティブを作って解決してみる
https://www.npmjs.com/package/vue-remove-whitespace
<div v-remove-whitespace> <span>1</span> <span>2</span> <span>3</span> <span>4</span> <span>5</span> </div> // 出力結果 // 12345その他の対応策
改行しなければスペースは入らない
<div> <span>1</span><span>2</span><span>3</span><span>4</span><span>5</span> </div>閉じタグの>を改行ればスペースは入らない
<div> <span>1</span ><span>2</span ><span>3</span ><span>4</span ><span>5</span> </div>開始タグの<tag以下を改行ればスペースは入らない
<div> <span >1</span><span >2</span><span >3</span><span >4</span ><span >5</span> </div>コメントアウトを挟めばスペースは入らない
<div> <span>1</span><!-- --><span>2</span><!-- --><span>3</span><!-- --><span>4</span><!-- --><span>5</span> </div>jsで改行を取り除けばスペースはなくなる(カスタムディレクティブでも同様のことやってる)
<div id="js_remove_nl"> <span>1</span> <span>2</span> <span>3</span> <span>4</span> <span>5</span> </div> <script> const removeWhiteSpaceDom = document.querySelector('#js_remove_nl') removeNlDom.innerHTML = removeWhiteSpaceDom.innerHTML.replace(/\s*<("[^"]*"|'[^']*'|[^'">])*>\s*/g, match => match.trim()) </script>cssでfont-sizeを0にすれば見た目上スペースはなくなる(コピペにスペースは入り込む)
<div class="remove_whitespace"> <span>1</span> <span>2</span> <span>3</span> <span>4</span> <span>5</span> </div> <style> .remove_whitespace { font-size: 0; } .remove_whitespace > span { font-size: initial; } </style>float: left(コピペにスペースは入り込む)
<div class='float_left'> <span>1</span> <span>2</span> <span>3</span> <span>4</span> <span>5</span> </div> <style> .float_left:after { content: ""; display: block; clear: both; } .float_left > span { float: left; } </style>display: flex(コピペに段落は入り込む)
<div class='flex'> <span>1</span> <span>2</span> <span>3</span> <span>4</span> <span>5</span> </div> <style> .flex { display: flex } </style>余談
英語圏だと、単語の間にスペースが入るのが基本だから、問題視されないのかなーという
- 投稿日:2019-08-29T19:12:08+09:00
SPA向けのCSS設計 - COCSS
Component Oriented CSS
CSSをもっと便利に使うための努力
CSSの歴史は、言うまでもなく長いです。
テーブルレイアウトの時代から使われ、テンプレートエンジンの時代を経て、JSが定着した最近はフレームワークやライブラリーなどにテーマが移っています。もはやウェブ開発者・ウェブデザイナーと言う概念から、FE・BE、細かい場合はマークアップエンジニアなど、専門的かつ細分化になっています。
CSSも、専門のエンジニアが活躍する場合もあるくらいです。こういう流れとともに、CSSの構造に関しても様々な工夫が行われてきました。
代表的には下記の例があります。
- OOCSS
- BEM
- SMACSS
- FLOCSS
- RSCSS
各例の概念と、不便なところだけ軽く見てみます。
経験者の方は、共感する部分もあると思います。既存の設計、これが不便だった
1. OOCSS
コンテナーとコンテンツで分けてコンテンツはコンテナーから独立している。
基本構造と見た目を分けて考える(再利用性向上)。作業して不便だと思った点
- コンテナーとコンテンツの基準が主観的。
- コンテンツがまたコンテンツを持つ場合の判定が難しい。
- クラス名が被ることが多い。
2. BEM
Block(基となる要素)、Element(Block内の子要素)、Modifier(変化した状態)に分けられる。
作業して不便だと思った点
- BlockとElementの区分が主観的。
- リファクタリングなどでElementがBlockに変わる場合に対応が難しい。
- Modifierの選定基準が曖昧。
- ある要素の変化を動作として見なす場合もある。
__
、--
などを使って、クラス名が長くなる。3. SMACSS
Base、Layout、Module、State、Themeに役割を分ける
作業して不便だと思った点
- プレフィックスなどルールが増える。
- 階層が増えると、クラス名も長くなる。
4. FLOCSS
Foundation、Layout、Objectで分けます。
作業して不便だと思った点
- 小規模サイト・サービスでは運用が難しい(パーフォマンスが悪い)。
- 徹底に守ると、とりあえずファイルが多くなる。
- プレフィックスと
__
、--
などを併用して使うので、クラス名も長くなる。5. RSCSS
Components、Elements、Variant、Layout、Helpersに役割を分ける
作業して不便だと思った点
Componentsは必ず2単語以上で組み合わせないといけない。
Componentsの中にComponentsが入る場合、Elementsの中にElementsが入る場合わかりづらい。
リファクタリングなどでElementsがComponentsに変わる場合に対応が難しい。だったら、改善していこう
ファイル構造はシンプルに
概ね下記の4つに分けられます。
- setting.css(sass)
リセットCSSの役割と、Sassなどだと変数やmixinなどを定義します。
- style.css(sass)
ヘッダー、フッターなど全領域に渡るパーツで共通的に使うCSSを指定します。
- font.css(sass)、icon.css(sass)、svg.css(sass)...
各目的に合わせて外部から持ってくるCSSです。
あまり触ることがないことと、運用の手間を減らすため、ルール外とします。
- コンポネント内
コンポネント内で影響するCSSは、基本そのコンポネント内でScopeを限定して使います。
クラス名から始めよう
一番解消したいのは、クラス名の短縮と最適化です。
クラス名が長くなったり重複が発生しまうと
- CSS設計時に構造がわかりにくくなる。
- 作業時にパーフォマンスが悪い。
- JSと連携する際に把握しづらい。
- HTMLコード上で見づらい。
変化に強いかつ単純なルール
作業の大体は、修正や維持補修です。
新規事業の場合でも、何回も仕様は変わります。
やり直す度に構造を意識してしまうと、限った時間内に作業できなくなるので、大体の場合諦めます。
結果、あれでもこれでもない形でリリースされ、下記の二つでの一択です。
- いいケース:CSSを整理するため、また時間をかけます。
- 悪いケース:放置でしょう。
- 最悪のケース:「今回これ使ったからダメだった。。。次回はあれ使おう!」になったら、救いの手は遠がる限りです。
なので、この3つに集中しました。
ついでにコンポネントと言う発想が、ヒントとなりました。Component Oriented CSS
いよいよ本題に入ります。
CO-CSSと言う概念をご提案します。・CSS命名規則だけでなく、SPAのコンポネント構造に合わせた概念です。
・リファクタリングに強いです。
・クラス名が極端的に短くなります。簡単なVue製SPAアプリの構造を想定しましょう。
<template lang="pug"> section div.search Routes#from-route Routes#to-route Calendar div.-number-panel span.icon.icon-checkin div.-number-selectors NumberSelector.-hotel NumberSelector.-adult NumberSelector.-child NumberSelector.-baby hr.transparent Button.-submit(button_text="検索") </template>航空券、ホテルを検索する基本的な画面です。
出発地・到着地の入力コンポネントRoutes
、日程を決めるコンポネントCalendar
、数を決めるコンポネントNumberSelector
、パラメタやステータスを保存するコンポネントButton
で構成されています。ルールはいたって簡単です。
画面(コンポネント)の最上位階層のクラス名
- アルファベット小文字で始まる
- 1つの単語で構成する(.date)
- 2つ以上の単語で構成される場合は
-
で連結する(.date-today)最上位以下の階層のクラス名
-
から始まる- 2つ以上の単語で構成される場合は
-
で連結するこれで、各コンポネントの領域がHTMLソースでもわかりやすくなります。
コンテナーやコンテンツなど、主観的な概念を徹底に排除しました。
CSSの設計に終わらず、コードの構造もシンプルに反映できるので連携がしやすいです。
-
は、Sassなどでも良い区切りになります。構造と独立したパーツのクラス名
- アルファベット小文字で始まる
- 1つの単語で構成する(.transparent)
- 2つ以上の単語で構成される場合は
-
で連結する(.icon-checkin)単体に使うパーツは、コンポネントに拘束されないので、独立したクラス名を使います。icomoonなど外部のアイコンライブラリーを使う時に、他の設計だと、構造に合わせて名前を変えたりしないといけないですが、COCSSだとそのまま実装できるので、手軽に適用できます。
ID名
- アルファベット小文字で始まる
- 2つ以上の単語で構成される場合は
-
で連結するIDは、CSSで拘束しないことを原則にしているので厳しい規則は決めてないです。
ただ、他のクラス名などと違和感がないかつクラス名と区分するくらいで決めます。
IDは関数内で変数名として再指定して使う場合が多いので、特にJSの命名規則に揃わなくてもいいはずです。
むしろ変数名と職別できるので間違いする可能性は低くなります。変則的にコンポネントが追加された場合を考えてみましょう。
上記の場合では、部屋数や年齢別の人数を調整するため、NumberSelector
と言うコンポネントを追加しています。<template lang="pug"> div.number table tbody tr td.-title {{ title }} td button.-minus - td.-amount p {{ result }} {{ type }} td button.-plus + input </template>このコンポネントでも、最上位階層はアルファベットの小文字で始まっています。
なので、これが独立したコンポネントであることがコード上で明らかになります。結論
いくら有名なルールでも、いくら人気があるルールでも、今の自分の作業環境に合ってなかったらむしろ作業に悪影響します。
少人数で速くルールを熟知して短期間に成果を出したいと思いましたら、試してみたはいかがでしょうか?
- 投稿日:2019-08-29T18:30:31+09:00
Azure Bot Serviceでメッセージのタイトル省略を無効にする
問題
Azure Bot Serviceからのメッセージで、タイトル部分 (太字)が省略されてしまう場合がある。
(画像中の「Windows 10の基本事項について説明します...」の...以降が省略されている)関連記事: Azure QnA MakerとBot Serviceで歓迎メッセージ&よくある質問対応のチャットボットを作成する
対処法
Web chatの
richCardWrapTitle
プロパティをtrue
にすることで省略をオフにすることができる。
追加部分イメージ:const styleOptions = { richCardWrapTitle: 'true' }; window.WebChat.renderWebChat({ directLine: window.WebChat.createDirectLine({ secret: 'YOUR_SECRET' }), styleOptions }, document.getElementById('webchat'));クライアント側全体コード例:
<!DOCTYPE html> <html lang="en-US"> <head> <title>Web Chat: Send welcome event</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- This CDN points to the latest official release of Web Chat. If you need to test against Web Chat's latest bits, please refer to pointing to Web Chat's MyGet feed: https://github.com/microsoft/BotFramework-WebChat#how-to-test-with-web-chats-latest-bits --> <script src="https://cdn.botframework.com/botframework-webchat/latest/webchat.js"></script> <style> html, body { height: 100% } body { margin: 0 } #webchat { height: 100%; width: 100%; } </style> </head> <body> <div id="webchat"></div> <script> (async function () { // In this demo, we are using Direct Line token from MockBot. // Your client code must provide either a secret or a token to talk to your bot. // Tokens are more secure. To learn about the differences between secrets and tokens // and to understand the risks associated with using secrets, visit https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-authentication?view=azure-bot-service-4.0 // const res = await fetch('https://shnaga-qna-bot.azurewebsites.net/directline/token', { method: 'POST' }); // const { token } = await res.json(); // // We are using a customized store to add hooks to connect event const store = window.WebChat.createStore({}, ({ dispatch }) => next => action => { if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') { // When we receive DIRECT_LINE/CONNECT_FULFILLED action, we will send an event activity using WEB_CHAT/SEND_EVENT dispatch({ type: 'WEB_CHAT/SEND_EVENT', payload: { name: 'webchat/join', value: { language: window.navigator.language } } }); } return next(action); }); const styleOptions = { richCardWrapTitle: 'true' }; window.WebChat.renderWebChat({ directLine: window.WebChat.createDirectLine({ token: 'YOUR_TOKEN' }), store, styleOptions }, document.getElementById('webchat')); document.querySelector('#webchat > *').focus(); })().catch(err => console.error(err)); </script> </body> </html>※
YOUR_TOKEN
箇所をご自身のトークンに変更してください。結果
無事にタイトル部分が省略されずに表示されるようになりました。
参考文献
Web Chat hosted samples
Azure QnA MakerとBot Serviceで歓迎メッセージ&よくある質問対応のチャットボットを作成する
Azure Bot ServiceのWebチャット画面でクライアント側からイベントを送る
- 投稿日:2019-08-29T18:21:44+09:00
JavaScript学習メモ
デベロッパーツールの使い方
・デベロッパーツールの立ち上げ
GoogleChromeを開いてからoptionキー + commandキー + i
・Consoleの改行
shiftキー + Enterキー
・ダイアログに出力
alert(〇〇〇); // 出力結果: ダイアログに()内の文字が出力・コンソールに出力
console.log("〇〇〇"); // 出力結果: コンソールに(" ")内の文字が出力識別子(変数名、関数名、メソッド名)のルール
1.半角のアルファベット
2.頭文字に数字は使えないが _ と $ はオッケー
3.予約語は使えない(下記参照)
break case catch class const continue debugger default delete do else enum export extends finally for function if import implements in instanceof interface let new package private protected public return static super switch this throw try typeof var void while with yield null true false
・コメントの記入
//一行だけ /* 複数行 */・エスケープ
文字列の途中に " を表示させたい場合 → \" と記入
("〇〇〇 \" 〇〇〇") // 出力結果: 〇〇〇"〇〇〇文字列を改行させたい場合 → \n と記入
("〇〇〇 \n 〇〇〇") // 出力結果: 〇〇〇 // 〇〇〇・文字列の結合
("〇〇〇"+"〇〇〇") // 出力結果: 〇〇〇〇〇〇・変数の代入
var 変数名 = "数値、文字列など";関数
・関数の作成
function 関数名(引数) { 処理 }・関数の呼び出し
関数名();・関数に結果をもどす
function 関数名(引数) { 処理 return 戻り値 }配列
・配列の作成
var 変数名 = [○,△,□,×];・配列の呼び出し
変数名[番号] //例 console.log(変数名[番号]); ※番号は0から・既存の配列の要素を使って新しい配列を作る場合sliceを使う
変数名.slice(最初の番号,最後の番号) // ※最初の番号だけ記載するとそれ以降の要素が全部よびだされる・配列の要素追加するときはpushを使う
変数名.push //例 console.log(変数名.push(新しい要素));・配列を順番に処理するときはforEachを使う
変数名.forEach(引数){ 処理 }・既存の配列の要素を全て新しい配列に作り直す場合mapを使う
変数名.map(引数) // ※returnで値を戻す必要あり・要素を条件によって選別して新たな配列を返すときfilterを使う
変数名.filter(関数の引数) // ※returnで値を戻す必要ありfor文
・for文の作成
for (初回だけ実行する処理; ループ継続条件; 毎ループ後の処理) { 毎ループごとに実行する処理 }
記述 処理 変数名.length 配列の要素の数の取得 変数名++ インクリメント 1ずつ増やす 変数名-- デクリメント 1ずつ減らす if文
・if文(条件分岐)
if ( 条件 ) { // ※条件の前に!つけると反転。〜であるとき→〜でないとき 条件を満たす場合に実行される処理を、この中に記述する } else { 条件を満たさない場合に実行される処理を、この中に記述する }・AND : 条件を追加したい場合 && をつかう。
( A && B ) // AとBに当てはまるとき・OR : 条件をどちらかに当てはまる場合にしたい場合は || をつかう。
( A || B ) // AかBどちらかに当てはまるときオブジェクト
・オブジェクトの作成
var 変数名 = { プロパティ名1 : "値1", プロパティ名2 : "値2", プロパティ名3 : "値3" }・オブジェクトの使用(ドット記法)
変数名.プロパティ名 // ※例 console.log(変数名.プロパティ名);・もうひとつの方法(ブラケット記法)※動的に使える
変数名[プロパティ名] // ※例 console.log(変数名["プロパティ名"]);・プロパティの値の変更したい場合
変数名.プロパティ名 = "新しい値";・プロパティを追加したい場合
変数名.新しいプロパティ名 = "新しい値";・プロパティを削除したい場合
delete 変数名.プロパティ名;・プロパティに関数を入れることも可能
変数名.プロパティ名 = function() { 処理 }・同じオブジェクト内のプロパティを使うときはthisをつかう
this.プロパティ名・一度セットした値をリセットしたい場合
変数名 = null;NaNについて
数字ではないという意味。isNaN()を使うと数値かどうかを判断できる
例
if (isNaN(引数)){ 処理 }ついでに : コンソールにundefinedと出る場合=定義されていないという意味
非同期処理
・非同期処理について
setTimeoutを使うと指定した時間後に処理が実行される。例
setTimeout(function() { 処理 },時間); // ※1秒=1000ミリ秒・clearTimeoutを使うと設定したタイマーをキャンセルできる
例
var 変数名 = setTimeout(function() { 処理 },時間); clearTimeout(変数名);
- 投稿日:2019-08-29T18:04:25+09:00
Azure Bot ServiceのWebチャット画面でクライアント側からイベントを送る
問題
Azure Bot Serviceのwebチャット画面にアクセスした際にウェルカムメッセージが表示されない場合がある。
関連記事: Azure QnA MakerとBot Serviceで歓迎メッセージ&よくある質問対応のチャットボットを作成する
対処法
クライアント側の方でBot serviceに
WEB_CHAT/SEND_EVENT
イベントを送り、OnMembersAddedAsync()
ハンドラを作動させる。コード例:
<!DOCTYPE html> <html lang="en-US"> <head> <title>Web Chat: Send welcome event</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- This CDN points to the latest official release of Web Chat. If you need to test against Web Chat's latest bits, please refer to pointing to Web Chat's MyGet feed: https://github.com/microsoft/BotFramework-WebChat#how-to-test-with-web-chats-latest-bits --> <script src="https://cdn.botframework.com/botframework-webchat/latest/webchat.js"></script> <style> html, body { height: 100% } body { margin: 0 } #webchat { height: 100%; width: 100%; } </style> </head> <body> <div id="webchat"></div> <script> (async function () { // In this demo, we are using Direct Line token from MockBot. // Your client code must provide either a secret or a token to talk to your bot. // Tokens are more secure. To learn about the differences between secrets and tokens // and to understand the risks associated with using secrets, visit https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-authentication?view=azure-bot-service-4.0 // const res = await fetch('https://webchat-mockbot.azurewebsites.net/directline/token', { method: 'POST' }); // const { token } = await res.json(); // We are using a customized store to add hooks to connect event const store = window.WebChat.createStore({}, ({ dispatch }) => next => action => { if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') { // When we receive DIRECT_LINE/CONNECT_FULFILLED action, we will send an event activity using WEB_CHAT/SEND_EVENT dispatch({ type: 'WEB_CHAT/SEND_EVENT', payload: { name: 'webchat/join', value: { language: window.navigator.language } } }); } return next(action); }); const styleOptions = { richCardWrapTitle: 'true' }; window.WebChat.renderWebChat({ directLine: window.WebChat.createDirectLine({ token: 'YOUR_TOKEN' }), store, styleOptions }, document.getElementById('webchat')); document.querySelector('#webchat > *').focus(); })().catch(err => console.error(err)); </script> </body> </html>※
YOUR_TOKEN
箇所をご自身のトークンに変更してください。参考文献
Web Chat hosted samples
Azure QnA MakerとBot Serviceで歓迎メッセージ&よくある質問対応のチャットボットを作成する
- 投稿日:2019-08-29T16:28:49+09:00
思い付きコード(後日解説)
<select name="select" id="sample" onchange="SendAjax(this);"> <option value="user_name1">user1</option> <option value="user_name2">user2</option> <option value="user_name3">user3</option> </select> <input type="text" name="username" id="user_name1" value="name1"> <input type="text" name="username" id="user_name2" value="name2"> <input type="text" name="username" id="user_name3" value="name3"> <script> document.getElementById("user_name1").style.display = "none"; document.getElementById("user_name2").style.display = "none"; document.getElementById("user_name3").style.display = "none"; function SendAjax(obj) { // objはselectタグ let idx = obj.selectedIndex; let value = obj.options[idx].value // value let text = obj.options[idx].text; // apper text const txt1 = document.getElementById("user_name1"); const txt2 = document.getElementById("user_name2"); const txt3 = document.getElementById("user_name3"); if(value != txt1.id || value != txt2.id || value != txt3.id) { txt1.style.display = "none"; txt2.style.display = "none"; txt3.style.display = "none"; } if(value == txt1.id) { txt1.style.display = "block"; } if(value == txt2.id) { txt2.style.display = "block"; } if(value == txt3.id) { txt3.style.display = "block"; } console.log(`TEXT: ${text} / VALUE: ${value}`); console.log(`txt_ID1: ${txt1.id}`); } </script>
- 投稿日:2019-08-29T16:21:12+09:00
Node.js上でCUIコマンドを用いたURLスクレイピング
Node.jsを用いたURLスクレイピング
はじめに
Node.jsを用いたスクレイピングは、
記事を漁るとしばしば見受けられます。
Webサーバを立ててリクエスト送るという記事は多いです。
それって、Node.jsの特徴をあまり活かせてないのでは?
と思い立ったためこの記事を書きました。Node.jsでスクレイピング
JSをサーバサイドでも動作させることができるプラットフォームですが、
JS単体では扱えなかった、CUIコマンドも扱えます。Webサーバ立てる必要なくない?
Curlコマンド利用してHTTPリクエスト生やして、
HTMLオブジェクトgetした上でスクレイピングしたほうがよくない?ということで
Node.js上でCUIコマンドを利用するためには、
child_processモジュール内のexecSync関数を利用します。余談ですが...
child_processモジュール内のexecSync関数は同期関数
child_processモジュール内のexec関数は非同期関数 なので注意。CUIExec(argv)関数では引数にURLを指定することで、
該当URLの飛び先にある、HTMLオブジェクト文字列を取得し、
戻り値に設定します。index.jsconst execSync = require('child_process').execSync; const CUIExec = (argv) => { // URL console.log(argv); return execSync("curl " + argv).toString(); }HTMLオブジェクトを取得したのちに、
正規表現によってhrefタグに紐づけられているURLのみを抽出し、
配列に格納します。index.jsconst urlParserIncludeHTML = (htmlString) => { return (htmlString+"").match(/href=\"(.*?)\".*?/gi); } if (process.argv[2]) { //コマンドライン引数の取得 let arrayIncludeURL = []; let uriList = []; uriList = urlParserIncludeHTML(CUIExec(process.argv[2])).slice(); console.log(uriList); for (let key in uriList) { let tmpStr = (uriList[key]+"").replace(/^href="/, "").replace('"', ""); //任意のファイル拡張子 if (tmpStr.match(".css") || tmpStr.match(".xml") || tmpStr.match(".png") || tmpStr.match(".jpg") || tmpStr.match(".svg") || tmpStr.match(".ico") || tmpStr.match(".json")) { continue; } else { if (tmpStr.indexOf("http") || tmpStr.indexOf("https")){ arrayIncludeURL.push(process.argv[2] + tmpStr); } else { arrayIncludeURL.push(tmpStr); } } } console.log(arrayIncludeURL); }実行コマンドは以下の通りです。
$ node index.js <URL>
index.jsをchild_processモジュールを利用し、
他のJSファイル上で実行することによって、
他言語との有意差はおいておいても、
JSのみでもソフトウェアが作成が可能です。
- 投稿日:2019-08-29T16:19:05+09:00
超初心者が受けるJS社内勉強会:第5回6回
続:実践
よくあるアコーディオンの処理について実践。
アコーディオン$(function(){ //ブラウザに読み込まれたとき 'use strict'; //エラー出力をするモードの宣言 $('.acc_button').on('click', function(){ //クリックした時 //console.log('Hello, world!'); //$contentという変数にクリックされたものの要素に代入する var $contents = $(this).closest('.js-accordion').find('.acc_contents'); if($contents.is(':animated')){ //$contentがアニメーション中だったら{}を実行 return false; //何もせずに処理を終了する } $contents.slideToggle(); //dd要素が表示されているときは閉じ、表示されていないときは閉じる $(this).toggleClass('open'); //クリックされた要素にopenクラスがついていれば削除。ついていなければ追加。 }); });$(this)
- イベントが発生(クリックした)した要素をさしている
.closest(selector)
- クリックしたとき
- その親要素をみる
- 子要素の中で、開く箇所をさがす
- スライドを開閉する
連打して開閉を続けてしまう場合の処理
- アニメーション中は何もしない、という命令を書き足す
return false;
- functionを終了する。
- falseを返す。
- 以降の処理を実行しない。
※if()の中身がtrueの場合に実行される。:animated
- アニメーションtrue、してないときはfalse
複雑なプログラムを組める人はすごいなあ、と思うのでした。
具体的な段階に入ると、脳の拒否反応が激しいので、このあたりにしようと思います。教えてくれた方々には感謝しています。
ありがとうございました。
- 投稿日:2019-08-29T15:28:43+09:00
【JavaScript】一旦初心に戻ってDOM操作を学び直す part02
なぜ学び直すのかとその目的
getElementByIdとか、querySelectorとかを使う簡単なDOM操作には結構慣れてきました。
でも、複数のDOM操作をする場合にあまり効率よくできてないんじゃないかな?
と思うことがあります。itemやらバブリングやらを使いこなして、スッキリしたコードをかけるようになるべく再入門です。【今日のメインテーマ】
・イベント名の似ているややこしい違いについて
・属性名の取得って色々方法あるね前回→https://qiita.com/irico/items/18471dd3260f1297cabb
(HTMLコレクションとNodeコレクション、ノードウォーキングについて)【参考】
https://codeday.me/jp/qa/20190201/205623.html
・書籍「JavaScript本格入門」p293~p302イベントドリブンモデル
【基本のおさらい】
JavaScriptでよく行われる、ボタンが押された→イベント発生のようなプラグラミングモデルを
イベントドリブンモデル
と言います。そしてイベントの処理内容を定義している関数を
イベントハンドラー
と呼びます。わりにくいイベントまとめ
mouseup
とclick
とか、何がどのタイミング?がわかりにくいイベントが多いのでまとめておきます。マウスクリック系のイベント
mousedown
・・・要素上でマウスを押した時発生
mouseup
・・・要素上でマウスを離した時
click
・・・要素上でマウスをクリックした時mouseup ≠click です。
例えば、関係のない要素でマウスを押し、ターゲットの要素でマウスを離した場合、
mouseup
は実行され、click
は実行されません。絶妙ですね。マウス移動系のイベント
mouseenter/mouseleave
・・・対象の要素の出入り時に発生
mouseover/mouseout
・・対象の要素の出入り時 + 内側の要素の出入り時にも発生ちょっとわかりにくいのでコード付きで説明します。
html<div class="parent"> <div class="child"></div> </div>css.parent{ margin: 0 auto; display: flex; justify-content: center; align-items: center; width: 400px; height: 400px; background: black; } .child{ width: 200px; height: 200px; background: white; }jslet target = document.querySelector('.parent'); target.addEventListener('mouseover',()=>{ console.log('入った!'); }) target.addEventListener('mouseout',()=>{ console.log('でた!'); })
.child
を.parent
で囲み、.parent
にmouseover/mouseout
のイベントを付与しています。
↑こんな感じになる訳ですが、これをマウスで左端の白いとこから右端の白いところまでスーッと移動させると、
コンソールではこのようになります。
子要素に出入りする際も入った!
でた!
が表示されるようになるんですね。
(詳しく言うと、親要素から出るとき、子要素から出るとき共にmouseout
が働く
親要素に入るとき、子要素に入るとき共にmouseenter
が働く)
mouseenter/mouseleave
だと子要素には反応しません。間違えやすそうなイベント定義注意点
プロパティとして設定するときは、関数オブジェクト!(関数呼び出しではない!)
window.onclick = event(); //ダメな例 window.onclick = event; //正しいうっかり間違えそうですね。
プロパティとして設定するときは、1つの要素に登録できるイベントリスナーは1つだけ!
onXXXXイベント系の制約です。
addEventListener
を使いましょう。イベントハンドラーはDOMContentLoadedイベントハンドラーの配下におく!
DOMContentLoadedイベントハンドラー配下におくことで、画面全体のロードが終わってからイベントが実行できるようにします。
基本的にjsはbody閉じタグ直前におくので、要素が取得できない!
と言うことはないかもしれませんが、習慣づけることは大切です。(ただし、重たいサイトで最初のページの方のイベント、とかだったら話が変わるかも。臨機応変に)
DOMContentLoaded
と似たものでonload
イベントがありますが、こちらは
画像を含む読み込みが終わったタイミングで実行されます。
DOMContentLoaded
は画像の読み込みを待ちません。画像読み込みを待たなければそれだけ早く実行できるので、
画像の読み込み必要→onload
画像読み込み必要ない→DOMContentLoaded
で使い分けてください。属性の取得における属性名≠取得名問題
hrefを設定したければ
element.href ="http:~"と、大体が一致しているのですが、
一部属性名≠取得名のものも存在します。
有名なものではclassがありますね。element.className ='section'私は普段これでアクセスしてましたが、
getAttribute
、setAttribute
で取得すると、この不一致問題が解消され、属性名を動的に変更可能という良さがあるみたいです。let myHref = link.getAttribute('href'); //取得 myHref.setAttribute('href','http:~'); //設定コードが長くなるので、これも臨機応変ですね。
不特定の属性の取得
attributes
・・・特定の要素ノードの全ての属性を取得する。
全属性の戻り値ですが、NamedNodeMapオブジェクト
で返されます。初めて聞いた...
こいつはHTMLCollectionに似ていて、個別のノードに名前でもインデックス番号でもアクセスできちゃいます。
ただし、HTMLCollectionなどと違ってノードの順序を保守してないことに注意です。
NamedNodeMap.item(i).name
で属性名、
NamedNodeMap.item(i).value
で属性の値を取得できます。document.addEventListener('DOMContentLoaded',()=>{ let target = document.querySelector('.target'); let attrs = target.attributes; //属性リストを取得 for(let i = 0;i < attrs.length;i++){ let attr = attrs.item(i); console.log(attr.name + ':' + attr.value); } },false);このコードは全ての属性をコンソールで列挙しています。
addEventListener
の第三引数falseがきになったらこちらをどうぞ。
https://qiita.com/irico/items/8a23071719aa28e3c032
https://qiita.com/irico/items/3e3cd9fad4610b83cf87NamedNodeMapは属性の付け外しも思いのまま!
//つけるとき let title = document.createAttribute('title') title.value = '画像です' attrs.setNamedItem(title); //外すとき attrs.removeNamedItem('title');
- 投稿日:2019-08-29T15:19:08+09:00
JSで日数カウンター
- 恋人の誕生日は何日後かな?
- このタスクの納期まで、後何日あるのかな?
- 令和になってから何日経過したかな?
等々、今日を基準に、xxは何日後?何日前?と計算する機会は多いと思います。でも月によって日数が違ったりする関係上、計算がやや大変だったりします。そこで、JSでさくっと日数計算をしてみます。
index.html<!DOCTYPE html> <meta charset="UTF-8"> <title>日数カウンター</title> <input type="date" id="myDate"> <div id="myMSG"></div> <script> (function() { document.getElementById("myDate").addEventListener('change', function() { var today = new Date(), myDate = new Date(document.getElementById("myDate").value), myCount = today.getTime() - myDate.getTime(), result; myCount = Math.floor(myCount / (1000 * 60 * 60 * 24)); if (myCount > 0) { result = myCount + "日経過しました。"; } else { result = -(myCount) + "日後です。"; } document.getElementById("myMSG").innerHTML = result; }, false); }()); </script>
- 投稿日:2019-08-29T14:50:59+09:00
後で読みたい記事をブックマークレットを使ってワンクリックでSlackに積んでおく(CORS回避)
はじめに
情報収集をネット記事で行う方が多いと思いますが、良い記事を見つけたけど時間がなくてあとで読みたい時はどうしていますか?
私は対象記事のURLを記事を積んでおくためのSlackチャンネルに投稿しておいて、あとでまとめて読んでいます。
Slackに飛ばしておくとPCでもスマホでもすぐに確認できるので便利です。Slackに記事を積んでおく時、以前は下記のような手順で保存していました。
1. URLをコピー
2. Slackを開く
3. 記事チャンネルにURLを貼り付け
4. 投稿エンジニアは面倒くさがりの方が多いと思いますので、4ステップもあると改善したくなりますよね。
ということで手順を簡略化する方法を検討しました。方針
やりたいことは、PCとスマホで開いたネット記事をSlackへ送信です。
ちなみにスマホのブラウザには「共有」ボタンがあり、そこからSlackを選択して、、、と進んでいくとSlackへ通知することができます。
ただ、使ったことある方はわかると思いますが動作がもっさりしていたり多機能なので共有する先を選んだりでクリック数はあまり削減されません。そこで今回はブックマークレットで実現することにしました。
ブックマークレットであればPCでもスマホでも動作するはずです。
またChromeであればブックマークをPCとスマホで共有することもできるので便利です。作り方
Slack通知用URL取得
まずは通知するためのURLを取得します。
通知したいチャンネルの⚙️(チャンネル設定)をクリックして、「アプリを追加する」をクリックします。
incoming-webhookをインストールします。
(画像では「表示する」になっていますが、未インストールの場合「インストール」になっていると思います)
設定画面でチャンネルへの投稿を指定のチャンネルを指定します。
通知に必要なWebhookURLやその仕様もこのページに載っています。※このURLがバレると誰でも通知できるようになってしまうので管理は慎重に。
使い方や例なども載っているので親切ですね。
ブックマークレットを実装
ブックマークレットで行うことは下記。
1. 現在開いているページのURLを取得する。location.href
2. WebhookURLに必要な情報をつけてリクエスト下記のように実装しました。
エラーの時はアラートを表示するようにしています。javascript:(function(){ const url = ''; // TODO: set WebhookURL fetch(url, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ text: location.href }) }).then(response => console.log) .catch(error => alert('error!!')); })()それではChromeで動作確認。デベロッパーツールのコンソールに貼り付ければ簡単に動作確認できます。
Qiitaのトップページで動作確認してみます。https://qiita.com がSlackに通知されれば成功です。
実行したところ下記エラーが発生しました。
Access to fetch at 'https://hooks.slack.com/services/xxx/yyy/zzz' from origin 'https://qiita.com' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response. VM3507:3 POST https://hooks.slack.com/services/xxx/yyy/zzz net::ERR_FAILED (anonymous) @ VM3507:3 (anonymous) @ VM3507:11 A bad HTTP response code (404) was received when fetching the script.CORS(Cross-Origin Resource Sharing)に引っかかってしましました。。。
参考:https://developer.mozilla.org/ja/docs/Web/HTTP/CORSSlackのWebhookURLなんて色々なところから呼びたいんだから広く許可しておいてくれよと思ったが、なっていないものは仕方ない。
ajaxで呼べないならブックマークレットはきびしいか、、、と諦めかけていたんですがググってみたらstackoverflowに答えが載っていました。
'Content-Type': 'application/json'
を削除せよとのこと。
https://stackoverflow.com/questions/45752537/slack-incoming-webhook-request-header-field-content-type-is-not-allowed-by-acceということでcontent-typeを削除した下記ソースで実行しました。
javascript:(function(){ const url = ''; // TODO: set WebhookURL fetch(url, { method: 'POST', headers: { 'Accept': 'application/json' }, body: JSON.stringify({ text: location.href }) }).then(response => console.log) .catch(error => alert('error!!')); })()CORSの考察
結果的には1行消すだけで動くようになったので独力で解決したかったなーと思いつつ、動くようになった理由を考察。
stackoverflowになぜ動くようになるのか理由も書いてありますが、よくエラーメッセージを読んでみるとRequest header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.と書かれています。
preflightで許可されていないcontent-typeだと怒られているようです。
CORSの仕様を改めて読み返してみると、POSTの場合はpreflightを送らずに送る方法もあります。
そこに気づき、content-typeを色々いじって動作確認してみればよかったのですが、jsonなんだから'Content-Type': 'application/json'
は必須でしょと思って何も試さなかった。。。
改めて「エラーメッセージをきちんと読むこと」と「トライアンドエラー」の重要さを認識しました。ブックマークに登録
ブックマークレットが無事完成したのでブックマークを登録します。
私はChromeを使っているのでブックマークマネージャーを開きブックマークレットを追加します。
- PC(mac book / Chrome)
あとで読みたいページを開いて、登録したブックマークをクリックすることでSlackへ通知することができました。- スマホ(Android / Chrome)
単純にページを開いてブックマークをクリックしても動作しなかったのですが、アドレスバーにブックマーク名"toSlack"を入れると無事動作しました。 アドレスバーにブックマーク名を入れて起動できることを初めて知りました。結構便利!最後に
このブックマークレットで今まで4ステップかかっていた作業が1ステップになりました。
体感では1回10秒くらいかかっていた作業が1秒くらいになった気がします。
1日2記事あとで読みたいと思った場合、年間では(10 - 1) * 2 * 365 = 6,570秒 ≒ 1.8時間
の短縮ですね!やったね!!
(まあ、このブックマークレット作成に1.8時間以上はかかっているので実際に元がとれるのはずっと先ですが、色々知見が得られたのでよしとしましょう)
- 投稿日:2019-08-29T14:47:23+09:00
ローカル(file:///プロトコル)時に毎回マイクやカメラの使用許可をたずねられる問題を回避する
結論
Chromeの起動時オプションに
--use-fake-ui-for-media-stream
を追加する。
が次の許可を求めています
を無視する。
permissionが常に許可されるので留意すること。参考情報のほうにFirefox版の対処方法も書いているのでそちらも
(現状FirefoxでSpeechRecognition
はできないけれどカメラや普通の音声入出力には使えるはず)症状
HTTPサーバーに公開していた音声認識
var recognition = new SpeechRecognition(); // var recognition = new webkitSpeechRecognition(); recognition.start();をするWebページがあった。
http上ではstart()
タイミングで一度だけマイクの使用許可を求められ、
以後何度recognition.start()
しても再度使用許可を求められることがなかった。このHTMLをローカル上にダウンロードし、直接ファイルを開いた場合、
file:///C:/hogehoge
recognition.start()
するたびに使用の許可を求めるダイアログが表示されてしまった。解決方法は結論のとおり、常に許可するオプションで最初の使用許可ダイアログすらスキップする方法を取った。
recognition.start()
した時点で問答無用で許可されるので、ローカルだが一応カメラとか発言に注意か。
おそらく自動テスト用のオプションに思えるが、用途に合っていたのでOK試したけどだめだったもの
ローカルなら事前に
MediaDevices.getUserMedia()
などで許可を取っておかないとダメなのかな?と考えたが、特に関係なし。これも毎回許可を求められらた。参考
- google chrome - always accept webRTC webcam request - Stack Overflow
- WebRTCアプリケーションのテストの課題・解決方法について
- WebRTCなコードをE2Eテストする - console.lealog();
SpeechRecognition
に固執していてよい情報が取得できなかった。
「マイク 許可」などであさっていたが、WebRTCというキーワードは自発的に出てこなかったので以後気をかけるとよさそう。
とりたくなかった方法
- 投稿日:2019-08-29T13:45:09+09:00
睡眠タイプ診断テストを作ってみた ~ 夜寝れない人要チェックです
僕のポートフォリオ(過去に作った作品)を紹介するコーナーです。
●睡眠タイプ診断テスト
https://r4.quicca.com/~douraku/My%20Checker
ライオン型 ・ クマ型 ・ イルカ型 ・ オオカミ型
ヒトには、大体4種類の睡眠タイプ(クロノタイプ)があるらしい。
・朝起きるタイミングは、いつがいいの?
・夜寝る時間のベストは?
・運動のパフォーマンスが上がる時間帯 etc....
この睡眠タイプを知れば、上記のことが、科学的に全て分かっちゃう!
という話を聞きましたが、
それぞれのタイプ判定の方法がなかなかややこしい…。ならば、
「自分で診断テストアプリを作るしかねえ!!」
と一念発起。
僕がプログラミングで最初に作った作品です。
JavaScript/jQuery を主に使って作ってます。
最初の作品なので、結構しんどかったですが、
動いた時はマジで飛び跳ねて喜びましたねえ~!
アプリとしての「質」は置いておいて、
【診断結果】に出てくる「タイプ別の行動時間帯」とかは
科学的に根拠があるものなので、是非チェックして欲しいです!^^
- 投稿日:2019-08-29T13:35:22+09:00
【スロット】アナザーゴッドハーデスのGODを引く確率【確率】
みなさん、GOD引いてますか?
今年の12月でハーデスは撤去ですね、ってことで記事を書きます。
ロジック
GODを引く確率をp(=1/8192)とします。
n回回したときに、g回GODを引く確率は{}_n C_g(1-p)^{n-g}p^g\\ g \leq nとなります。
n回回したときに、少なくとも1回以上GODを引く確率は
1-(1-p)^{n}となります。
n回回したときに、少なくともg(≧1)回以上GODを引く確率は
1-(1-p)^{n}-\sum_{i=1}^{g-1} {}_n C_i(1-p)^{n-i}p^iとなります。
例1:
2000回回したときに、1回GODを引く確率\begin{align} &{}_{2000} C_1(1-1/8192)^{2000-1}(1/8192)^1\\ &\fallingdotseq0.1912748\\ &=19.12748{\%} \end{align}例2:
4000回回したときに、3回以上GODを引く確率\begin{align} &1-(1-1/8192)^{4000}-\sum_{i=1}^{3-1} {}_{4000} C_i(1-1/8192)^{4000-i}(1/8192)^i \\ &\fallingdotseq0.135081\\ &=1.35081{\%} \end{align}プログラム
プログラムはこちらから動かすことができます。
$('#calc').click(() => { const p = 1 / 8192; const n = parseInt($('#trial_number').val()); const g = parseInt($('#god_number').val()); if(n < g) { $('#probability').text('入力値が不正です'); return; } let gp; if($('input[name=type]:checked').val() === 'equal') { // GODを指定回数引く gp = math.combinations(n, g) * Math.pow(p, g) * Math.pow(1 - p, n - g); } else { // GODを指定回数以上引く if(g === 0) { $('#probability').text('入力値が不正です'); return; } let mgp = 0.0; for(let i = 1; i <= g - 1; i++) { mgp += math.combinations(n, i) * Math.pow(p, i) * Math.pow(1 - p, n - i); } gp = 1 - Math.pow((1 - p), n) - mgp; } gp *= 100; $('#probability').text(gp.toFixed(5) + '%'); });
- 投稿日:2019-08-29T12:06:49+09:00
化学式の係数を力任せに探索するPGM
化学式の係数をただ力任せに探索するだけのPGMです
githubURL : https://github.com/NanjoMiyako/MiteiKeisu
PGMのサンプルページ: https://nanjomiyako.github.io/MiteiKeisu/
処理の流れとしては
1.まず大まかにブロック別として探索する係数の範囲を決定
2.決定した係数探索範囲のブロックを元に係数候補のリストを作成
3.それらの係数候補から化学反応式としてOKなものか判定し、
OKだったらそれを返すようにする参考画像
- 投稿日:2019-08-29T11:34:06+09:00
【Vuetify on Nuxt.js】実際に本番運用したコンポーネントとそのカスタマイズ
概要
本スライドはVuetify Meetup #1での発表資料です。面白そうと思った方はぜひ次回以降参加してください!LTも募集中です。
自己紹介
業務内容
Twitter @Meijin_garden
名人って呼ばれてます。株式会社NoSchoolのCTO。
中高生のための勉強質問サイトを作ってます。
自分しかフルタイムのエンジニアがいないのでAWSもFirebaseもLaravelもNuxt.jsもデザインも採用も機械学習もやってます。
経歴
奈良高専情報工学科
↓
株式会社LIFULL
↓
株式会社NoSchool
Vuetify on Nuxt.jsを採用した理由
背景
2017年からNoSchoolのサイト自体はあるが、社長がWordPress on SakuraServerで起業したのでエンジニアから見たら酷いものだった。
シードの資金調達を終えて僕がジョインした2019年3月から5月にかけて、フルリプレイスを行うこととなった。
リプレイス後の技術構成
インフラ: AWS(CloudFront, ALB, EC2, S3...)
サーバーサイド: Laravel
フロントエンド: Nuxt.js, jQuery※2ヶ月じゃフロントまで移行やりきることができず、現在もページ単位でNuxtに移行中
Nuxt.jsの採用理由
- 僕自身がVue.jsの経験が1年強あった(前職の新規事業で1人でフルスクラッチした)
- jQueryと比較して開発速度が段違い
- ReactとVueだとあまり違いも採用力が衰えるということもなさそうなのでCTOの僕が経験があって開発速度がとにかく速いものを選べばいいやという考え
Vuetifyの採用理由
- デザイナー不在でそこそこの品質で開発をするためにUIフレームワークは必須
- Bulma、Bootstrap Vueなども見たが、Class属性ではなくコンポーネント自体を用意してくれている方が使い勝手がいいと思った
- ちなみにElementは前職で使っていたけどレスポンシブや細かいところでイライラした
実際にVuetifyで頑張ったページたち(2019/8時点)
家庭教師一覧
家庭教師詳細
質問作成
Vuetifyでまずこれだけ覚えておけば的なコンポーネントリスト
- ページ(
v-app
,v-content
等)- レイアウト(
v-layout
,v-flex
)- 余白(
.py-2
,.mx-4
等)- 装飾系(
v-card
,v-chip
,v-icon
)- 画像系(
v-img
,v-avatar
)- フォーム(
v-btn
,v-select
,v-text-field
等)- あとはドキュメントを読み込む。カスタムSlotも便利。
Vuetifyの便利コンポーネントをいくつか紹介
ページ構成(default.vue)
<template lang="pug"> v-app my-header v-content(app) my-breadcrumbs(:items="breadcrumbs") v-container(app fluid) nuxt my-footer </template>※ちょっと改変はしてます
v-app
やv-content
などは公式ドキュメントの通り。
パンくず(Breadcrumbs.vue)
パンくずはv-breadcrumbsがベースで、カスタマイズのために
v-slot:item
を利用。<template lang="pug"> v-breadcrumbs(:items="items").px-2.py-0.grey.lighten-3 template(v-slot:item="props") v-breadcrumbs-item( :to="props.item.to" :disabled="props.item.disabled" v-if="props.item.to !== '/'" ) span.grey--text {{ props.item.text }} span(v-else) a(href="/").pa-0 v-icon.body-1.primary--text mdi-{{ props.item.icon }} template(v-slot:divider) span > </template>
ページネーション
<template lang="pug"> v-pagination( v-model="pageModel" @input="onPaginationClicked" @next="onPaginationClicked" @previous="onPaginationClicked" :length="last_page" :total-visible="7" style="overflow-x: scroll;" ).my-4 </template>scriptを抜粋するとこんな感じ。
<script> export default { ... methods: { onPaginationClicked(event) { this.$emit("query-updated", { page: this.pageModel }); } } ... }; </script>要は、ページネーションのページ番号が変わったときに呼び出し元コンポーネントで再度API叩くなりする必要があるので、親にemitする設計にしている。
ページネーションの注意
total-visibleが偶数だと、中央あたりのページ番号で表示が変になるので注意。
例えば6にしたとき、全ページ数が10などのときに5ページ目付近で表示がおかしくなり、隣り合ったページが表示されない。
これは...も含めたvisibleのcountだから、偶数だと...の数が合わなくて隣り合ったページが出ない。
(気になるなら直せやOSSだろという話ですよねすみません)
Vuetifyをベースにカスタマイズしているコンポーネント
見出し系
Vuetifyの見出しはフォントサイズが大胆なのと、サイト全体で統一感を出したいので共通コンポーネント化。
plugin/globalComponent.jsimport Vue from 'vue' import Body from "@/components/layouts/Body"; import PageTitle from "@/components/parts/heading/pageTitle"; import SubTitle from "@/components/parts/heading/subTitle"; import SubTitle2 from "@/components/parts/heading/subTitle2"; Vue.component('sec-main', Body) Vue.component('page-title', PageTitle) Vue.component('subtitle', SubTitle) Vue.component('subtitle2', SubTitle2)悩ましいのは、マージンまでコンポーネントに入れるかどうか。入れるとしたらどれくらい大きいマージンを入れるか(デザインの話になってくるけど)。
ボタン
サイトを作っていく中で、
outlined
なv-btn
をよく使うので共通化。<template lang="pug"> v-btn(outlined large :color="color" :to="to" @click="clicked" :block="block").py-2.mt-2 v-icon(v-if="icon" :class="iconColor + '--text'").title.mr-2 mdi-{{ icon }} .subtitle-1 slot </template> 以下略
必須ラベル
最近作ったatoms的なのでかなり気に入ってる。
<template lang="pug"> v-chip(small label color="accent").px-2.font-weight-bold 必須 </template>
駆け足でしたがLTなのでこれくらいにします
最後に
NoSchoolではフロントエンドが得意で、Laravel等のサーバーサイドもゴリゴリやっていきたいWebエンジニアを募集中です!(iOSエンジニアも!)
回答最適化のために機械学習も取り入れていたり、勉強意欲の強い人が自由に暴れられると思いますので是非お声がけください〜。
- 投稿日:2019-08-29T11:11:20+09:00
Reduxを使い始めたらthisに悩まされなくなった
javascriptのthisは非常に癖が強く使う場合は注意が必要です。
最近はアロー関数のおかげである程度マシになりましたが、
逆に旧来のjsに慣れ親しんでいる人からするとthisが切り替わらないことが気持ち悪かったりもするようで、厄介なことに変わりありません。javascriptは後方互換性を大切にする言語なので、thisレベルの大きな言語仕様が変更されることはおそらくないでしょうし、
typescriptを使ってもjavascriptのスーパーセットである以上、thisはthisのままです。
これはいつまで経ってもフロントではthisに悩まされ続けるのかなぁとうんざりしていたのですが、
ふと気づくと最近thisに悩まされないどころか、書いてすらいないことに気づきました。何がきっかけなんだっけ?と振り返ってみたところ、どうやらReduxのおかげのようでした。
Reduxを使うと処理と状態が分離する
thisが必要になるのは、いわゆるオブジェクト指向的な、
状態を持ったオブジェクトに紐づく関数を使う場合です。しかし、Reduxを使うとStateはStoreに全て集約されます。
Reducerは純粋な関数ですし、ActionCreatorも引数に応じてActionをDispatchするだけ、
ContainerはStateからPropsを生成してPresentationalComponentに渡すだけ、
PresentationalComponentは当然FunctionalComponentなのでStateは持ちません。つまり、thisを使う必要のあるシーンがありません。
まとめ
- thisはStateとロジックが結合している場合に必要になる
- reduxはStateを一か所に集約するためthisが必要なくなる
- 投稿日:2019-08-29T10:33:29+09:00
関数型について勉強してみたらコードがシンプルになりました
こちらの記事を読ませていただき、とても勉強になったので実践した、というお話です。
忙しい人のためのjavascriptでざっくり理解する関数型結果として、大変勉強になりました!ありがとうございます。
それでは、どんな風に修正したのか、前回書いた記事で使ったコードで比較してみます。
修正前のコードfunction getMyQiita() { const token = "発行したアクセストークン"; const uri = "http://qiita.com/api/v2/authenticated_user/items/"; const options = { headers : { 'Authorization' : 'Bearer ' + Token }, method : 'get' } const res = JSON.parse(UrlFetchApp.fetch(uri, options)); const id = res.map( function(report) { return report.id }); const reports = id.map( function(id) { const uri = "http://qiita.com/api/v2/items/"; return JSON.parse(UrlFetchApp.fetch(uri + id, options)); }); const report_array = reports.map( function(report) { return { "title" : report.title, "likes_count" : report.likes_count, "page_views_count" : report.page_views_count }; }); var msg = ""; report_array.map( function(array) { msg += array.title.slice(0,30) + "\n / いいね : " + array.likes_count + " / view : " + array.page_views_count + "\n"; }); return msg; }完成したときは嬉しかったのですが、こうしてみると長いですし、何度も const 宣言してますしおすし。
だいたい、report_array
ってなんだよ!(; ・`д・´)
変数名に苦心し過ぎだろ!
あー、恥ずかしい(*ノωノ)それでは、勉強の成果をみてください。(=゚ω゚)ノ
修正後のコードfunction getMyQiita() { const token = "発行したアクセストークン"; const uri = "http://qiita.com/api/v2/authenticated_user/items/"; const options = { headers : { 'Authorization' : 'Bearer ' + token }, method : 'get' } return JSON.parse(UrlFetchApp.fetch(uri, options)) .map( function(report) { return report.id }) .map( function(id) { const uri = "http://qiita.com/api/v2/items/"; return JSON.parse(UrlFetchApp.fetch(uri + id, options)); }).reduce( function(prev, report) { return prev + report.title.slice(0,30) + "\n / いいね : " + report.likes_count + " / view : " + report.page_views_count + "\n"; },""); }変数の数があからさまに減りました。
const
が 7個から 3個に。var
はなくなりました。
まさに劇的ビフォーアフター。凄く単純なことなんですが、
map
関数って.
で繋ぐことができるんですね。
「知らなかったの?」と言われそうで恥ずかしいのですが、この気づきで変数がほぼ不要になりました。あと、配列を一文の
string
にするためにmsg
という変数を用意して毎回追記していたのですが、reduce 関数を使ってこれも不要になりました。
reduce
関数は初めて知ったのですが、配列内の要素の値を1つにまとめてくれます。
初期値の設定もできるので、応用度が高いです。
色々なところで使っていけそうです。良いものを知りました。('◇')ゞおわりに
ここまでお付き合いいただきありがとうございました。
まずは動かすことが大事だから、自分のわかる範囲で作るのですが、新しい知識が手に入ると昔のも見直したくなりますね。
で、「ぎゃー!昔の自分はなんて稚拙なんだぁぁぁぁぁ(*ノωノ)」ってなるやつです。
子どものときの読書感想文みたいですね。どーでもいい話ですが、うちの息子は夏休みの宿題の読書感想文がまだ終わっていません。
彼曰く、最高難易度の宿題なんだとか。
真面目に仕上げようと思っているだけでも偉いな、と思います。ではまた!(^^)/
参考にさせていただきましたm(_ _)m
- 投稿日:2019-08-29T06:32:57+09:00
JavaScriptで100個のボールを描画してみた
JavaScriptで簡単に何か描画してみようと思い、画面に流行りのタピオカを散らばらせてみました。
HTML
ブラウザの画面のランダムな位置に100個ボールを散らばらせる。
ボールは「●」で表し、位置はCSSでオブジェクトのプロパティと結びつける。<!DOCTYPE html> <html> <head> <meta charset='utf-8'> <title>サンプル</title> <link rel='stylesheet' href='./main.css'> </head> <body> <div>●</div> <script src='./main.js'></script> </body> </html>JavaScript
まず、「動くもの」クラスを定義する。
class movable { constructor(x, y) { this.pos = { x: x, y: y }; this.move = (x, y) => { this.pos.x += x; this.pos.y += y; }; } }次に、ボールオブジェクトを格納する空の配列を作る。
let ball = [];ボールオブジェクトのインスタンスを生成する。
for (let i=0; i<=100; i++){ ball[i] = new movable( Math.floor(Math.random()*window.innerWidth), Math.floor(Math.random()*window.innerHeight) ); }最後に、ボールオブジェクトをブラウザに描画する。
for (let i=0; i<=100; i++) { document.write('<div class="ball" style="top:' + ball[i].pos.y + 'px;left:' + ball[i].pos.x + 'px;">●</div>'); }CSS
.ball { position: absolute; }リロードするたびに「●」の位置がランダムに変わります。
- 投稿日:2019-08-29T05:27:51+09:00
オンラインエディタのススメ
phpでのwebアプリ開発を効率的に進めていくために、オンラインエディタを作りましたのでその紹介です。
B-code
B-codeはブラウザを使ってサーバ上のファイルを直接編集するためのツールです。もうFTPは要りません。
インストール
いきなりインストールです。
B-codeはデータベースを使いません。phpがあれば動きますのでまずはインストールしてみてください。1 githubからソースコードをダウンロードしてください。
https://github.com/TakaoNishidaBBT/B-code2 解凍したファイルをFTP(ここだけ必要です)でwebサーバにアップしてください。
3 アップしたディレクトリ直下にinstallというディレクトリがありますのでブラウザで開きます。アップロードしたディレクトリがhttp://hoge.com/bcode/の場合、http://hoge.com/bcode/install/となります。
インストール画面が開きますのでまずは、一番下の「ディレクトリパーミッションの確認」の赤くなっている行がない状態にしてください。そのあと、Basic認証、管理者アカウントをそれぞれ設定し、「確認」→「インストール」と進んでください。
設定した情報でログインします。
PROJECT
PROJECT管理画面がログイン後、最初に開きます。
「新規登録」ボタンからPROJECTを作成します。ドメインとDoc Rootは、B-codeがインストールされているところがデフォルトで表示されています。
B-codeは、マルチドメインにも対応していますので、その場合はここを変更してください。各項目を設定し、「登録」ボタンをクリックするとPROJECTが作成されます。
「OPEN」ボタンをクリックしてエディタを起動します。
EDITOR
空のPROJECTに既存のファイルをまとめてアップロードするにはzipファイルを使います。
ファイルの全選択はctl+a、削除はdeleteキーでできます。
あとは、ファイルを選択して編集するだけ。
もうFTPは要りません。
- 投稿日:2019-08-29T00:25:48+09:00
wasmのRustではVec型をreturnできない
詰まったこと
現在個人開発しているサービスで,文字列を処理するスクリプトをwasmで書こうとしていた。
extern crate wasm_bindgen; use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn text_parser(text: &str) -> Vec<String>{ let mut formatted_text = vec![]; // textをパースしてformatted_textに格納していく return formatted_text; }これをコンパイルしようとしたところ以下のエラーが発生
error[E0277]: the trait bound `std::boxed::Box<[std::string::String]>: wasm_bindgen::convert::IntoWasmAbi` is not satisfied --> src/lib.rs:4:1 | 4 | #[wasm_bindgen] | ^^^^^^^^^^^^^^^ the trait `wasm_bindgen::convert::IntoWasmAbi` is not implemented for `std::boxed::Box<[std::string::String]>` | = help: the following implementations were found: <std::boxed::Box<[f32]> as wasm_bindgen::convert::IntoWasmAbi> <std::boxed::Box<[f64]> as wasm_bindgen::convert::IntoWasmAbi> <std::boxed::Box<[i16]> as wasm_bindgen::convert::IntoWasmAbi> <std::boxed::Box<[i32]> as wasm_bindgen::convert::IntoWasmAbi> and 9 others = note: required because of the requirements on the impl of `wasm_bindgen::convert::IntoWasmAbi` for `std::vec::Vec<std::string::String>` error: aborting due to previous error For more information about this error, try `rustc --explain E0277`. error: Could not compile `text_formatter`. To learn more, run the command again with --verbose.公式のIssueを見たところ、どうもwasmを使う場合返り値にVec型を指定できないらしい
対策
extern crate wasm_bindgen; use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn text_parser(text: &str) -> String{ let mut formatted_text = vec![]; // textをパースしてformatted_textに格納していく return formatted_text.iter().cloned().collect::<String>(); }今回は単純にStringにまとめてもJS側でどうにかできたのでStringにまとめて返り値にした。
そうは行かない場合、JSON形式にまとめてStringにしてから返り値にするなどの対策がある。しかしなかなか面倒な処理が必要となるので、できれば早く公式の方で対応してほしいところである。