- 投稿日:2020-08-08T22:07:17+09:00
【JS】JSを根本から再復習、関数とかオブジェクトとか
関数
JSの世界では、関数は実行可能なオブジェクトです。
具体的には、関数を引数として渡すことができたりします・。コールバック関数
function hello() { console.log('hello'); } // 引数のcallbackに hello が渡ってくる // この引数のことを callback 関数と呼びます function sample(callback) { callback("yo"); } sample(hello); // 無名関数だと下記のような書き方になる sample(function(name) { console.log('hello ' + name); });this
実行コンテキストによって
this
の参照先は変わることになります。呼び出し元の参照を保持し、
オブジェクト → 呼び出し元のオブジェクト
関数 → グローバルオブジェクトを指します。
callbackも関数に含まれます。bind
this
や引数の参照先を変更し、使用時点で実行しません。束縛するとは?
this
を固定するで理解しています。
JavaScript の超便利なメソッド bind で this を制御する – Foreignkey, Inc.
関数バインディングcall, apply
thisや引数のの参照先を変更し、使用時点で実行します。
アロー関数
無名関数を記述しやすくしたもので、
() => {};
のように書くことができます。無名関数とアロー関数の違い
アロー関数は、
this
やarguments
の値を取りません。
ではどうやってthis
の値を取ってきてるかというと、外側のthis
を探しにいくことで値を取得することができるようになっています。
なので、アロー関数で定義した関数はbind
を使ってthis
を束縛することはできないことに注意します。prototype
- オブジェクトに存在する特別なプロパティ
- prototypeの参照が、
__proto__
に コピーされる- prototypeを使用する理由は、メモリの効率化
- ES6になってからは、裏側でおごいているのがこいつになる
プロトタイプチェーン
プロトタイプの多重形成のことを指します。
下記のように
__proto__
が層上になっている状態をプロトタイプチェーンと言います、
優先順位
基本は階層が浅いものから呼び出されることになります。
- 自分自身のプロパティ
- コンストラクターが持っているメソッド
- 他にprototypeを持っているか確認していく
- Objectに格納されているプロパティ(ここにもなければ、undefined)
クラス
ES6からクラス表記でかけるようになっています。
class Person { // コンストラクタ constructor(name, age) { this.name = name; this.age = age; } // Person.prototype.hello = function() { ... と同じことを表す hello() { console.log('hello ' + this.name); } } // インスタンス化 const bob = new Person('Bob', 23); console.log(bob);コンストラクタ
新しくオブジェクトを作成するための土台となるものを指します。
インスタンス化
new
を使用し、コンストラクタ関数からオブジェクトを作成することを指します。クラス継承と super
class Person { constructor(name, age) { this.name = name; this.age = age; } hello() { console.log('hello ' + this.name); } } // 継承には extends を使用します class Japanese extends Person { constructor(name, age, gender) { // 継承元(Person)のコンストラクターを呼び出すことができる // コンストラクターを呼び出しているため、name, ageを引数として渡している super(name, age); this.gender = gender; } hello() { console.log('Konnichiwa ' + this.name); } }オブジェクトリテラル
{}を使った初期化のことを指します。チェーンメソッド
言葉の通り、メソッドを繋いでいくことを指します。
class Calculator { constructor() { this.val = null; // アンダーバーはこのクラス内でしか使用しないという意味です。 this._operator; } set(val) { if(this.val === null) { this.val = val; } else { this._operator(this.val, val); } // チェーンを実装するためには、Calculatorのオブジェクトを返す必要がある return this } plus() { this._operator = function(val1, val2) { const result = val1 + val2 this._equal(result) } return this } minus() { this._operator = function(val1, val2) { const result = val1 - val2 this._equal(result); } return this } _equal(result) { this.val = result console.log(result) return this } } // インスタンス化 const calc = new Calculator(); // チェーンメソッド calc.set(10) .plus() .set(3) .minus() .set(4)ビルトインオブジェクト
- String, Date, Numberとか
- Javascriptエンジンによって生成される
- JSの場合は、
String()
のように宣言するのではなく、 変数にstring
を入れてあげることで実行されるラッパーオブジェクト
- プリミティブ値を内包するオブジェクト
- new しなくても暗黙的に
シンボル
プロパティの重複を避けるために必ず一意の値を返す関数のことを指します。
スプレッド演算子
ES6だと、可変長パラメータはスプレッド演算子で表現します。
...arry
のような書き方
スプレッド構文 - JavaScript | MDN
- 投稿日:2020-08-08T19:16:43+09:00
【JS】デフォルトで用意されているダイアログ
警告のメッセージを表示する alert()
alert("Hello");Google chromeで、JSONデータをアラートで表示したい時
JSONデータ(data)だけを引数にしたalertメソッドだと、「Object」としか表示されない
alert(JSON.stringify(data));こうすることで、JSONデータをアラート表示できるようになる
クローズドクエスチョンを表示する confirm()
はい、いいえかで答えられるメッセージを表示させます。
confirm(メッセージ);実際によく使うのは、メッセージ対する返答によって処理を変えることです。
let result = confirm("このメッセージを送信しますか?"); if(result){ //「はい」を選んだ時の処理 }else{ //「いいえ」を選んだ時の処理 }オープンクエスチョンを表示する prompt()
具体的な返答が求められるメッセージを表示させます。
let result = prompt(入力ダイアログに表示する文字列, 入力欄の初期値)confirmメソッドと同様、実際によく使うのは、メッセージ対する返答によって処理を変えることです。
let name = prompt("あなたの名前は?", '太郎') if(name){ //入力された時の処理 }else{ //キャンセルが押された時の処理 }計算の答えによって、アラートを変える
let answer = prompt('1 + 1は?') if (answer === 2){ alert('正解です!') } else { } if(answer === ''){ alert('空欄で回答しないでください') }else if(answer === null){ alert('キャンセルしないでください') }else if(answer != 2){ alert('不正解です') }else{ alert('正解です!') }
- 投稿日:2020-08-08T18:45:03+09:00
bottlejs hello ?
動機
自作のSPAをスクラップ&ビルドするので今度はDIを使ってみようと思ったけどinverify はなんだか面倒くさそうなので bottlejs を使う。
前準備
Webpackとnpm install --save bottlejs とかやっておいた。
実装
index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Kamishibai framework</title> </head> <body> <!-- IE11 promise --> <script src="https://www.promisejs.org/polyfills/promise-7.0.4.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.4/fetch.min.js"></script> <script src="/js/bundle.js"></script> </body> </html>app.jsimport Bottle from 'bottlejs' window.kms = window.kms||{}; window.kms.Unko=function(){ this.color = "brown"; } window.kms.rootBottle = new Bottle(); window.kms.rootBottle.service("?",window.kms.Unko); window.kms.unko = window.kms.rootBottle.container["?"]; alert("?の色は"+window.kms.unko.color);結論
見事にボトルに?を詰めておくことができた。
これで何ができるかってことだけど、処理の流れとオブジェクトの作られ方が分離できるってことかなと思う。
例えば、その?に?がついていたとしても、ロジック側でその設計を考える必要がなくなるというか。あらかじめ?付きの?をボトルに詰めときゃいいじゃんっていう。その用途だったらかなり古いけど bottlejs で十分機能するかなと思って実験中。
- 投稿日:2020-08-08T18:09:41+09:00
JavaScript ストップウォッチを作成
JavaScriptで簡単なストップウォッチを作ってみました。
機能的には基本のスタート/ストップのほか、ラップタイム/スプリットタイムも付けました。CSS、JavaScriptともに必要最小限の記述しかしていませんので処理も追いやすいかと思います。
index.html<!doctype html> <html lang='ja'> <head> <meta charset='utf-8'> <meta name='viewport' content='user-scalable=no,width=device-width,initial-scale=1'> <title>stopwatch</title> <style> html { touch-action: manipulation; -webkit-touch-callout: none; -webkit-user-select: none; } div.button { box-sizing: border-box; position: relative; width: 100%; text-align: center; } input[type='button'] { width: 48%; height: 50px; max-width: 180px; font-size: 25px; border-radius: 8px; background-color: #fff; -webkit-appearance: none; -webkit-user-select: none; } div#disp { position: relative; text-align: center; font-size: 20px; margin-bottom: 20px; } div#lap { position: relative; text-align: left; font-size: 18px; height: 300px; overflow-y: scroll; width: 340px; min-height: 200px; margin-top: 60px; margin-bottom: 10px; left: 38px; } div#mask { position: relative; width: 320px; overflow: hidden; margin: auto; } div#lap>div { height: 30px; } #container { width: 100%; text-align: center; } </style> </head> <body> <div id='container'> <div id='mask'> <div id='lap'></div> </div> <div id='disp'>0:00:00.000 / 0:00:00.000</div> <div class='button'> <input type='button' id='start' value='START'> <input type='button' id='reset' value='RESET'> </div> </div> <script src='./stopwatch.js'></script> </body> </html>stopwatch.js"use strict"; const lapCount = 50; // ラップ保持数 const storage = getStorage(); let state = storage.state ?? 0, // 動作状態 0:停止中 1:動作中 startTime = storage.startTime ?? 0, // スタートタイム保持 stopTime = storage.stopTime ?? 0, // ストップタイム保持 lapTime = storage.lapTime ?? 0, // ラップスタートタイム保持 lapStopTime = storage.lapStopTime ?? 0, // ラップストップタイム保持 id; // setInterval ID onload = function() { // localStorageに動作状態が保存されていた場合はパラメータ復元 if(state === 1) { if(id = setInterval(printTime, 1)) { document.querySelector('#start').value = 'STOP'; document.querySelector('#reset').value = 'LAP'; } setStorage(); } } const eventHandlerType = document.querySelector('#start').ontouchstart !== undefined ? 'touchstart' : 'mousedown'; // START押下時イベント document.querySelector('#start').addEventListener(eventHandlerType, function() { // 停止中 if(state === 0) { // カウント開始 if(id = startCount()) { // ボタンのラベル変更 document.querySelector('#start').value = 'STOP'; document.querySelector('#reset').value = 'LAP'; // 動作状態を変更 state = 1; setStorage(); } } // 動作中 else { // カウントインターバルが動作中 if(id) { // インターバル停止 clearInterval(id); // カウント停止 stopCount(); // ボタンのラベルを戻す document.querySelector('#start').value = 'START'; document.querySelector('#reset').value = 'RESET'; // 動作状態を変更 state = 0; deleteStorage(); } } }, false); // RESET押下時イベント document.querySelector('#reset').addEventListener(eventHandlerType, function() { // 停止中ならリセット if(state === 0) { stopTime = 0; lapStopTime = 0; // 表示初期化 document.querySelector('#lap').innerHTML = ''; document.querySelector('#disp').textContent = '0:00:00.000 / 0:00:00.000'; } // 動作中ならLAP動作 else { // #lap内の最後にdiv要素追加 document.querySelector('#lap').appendChild(document.createElement("div")); // 追加した要素に経過時間表示 document.querySelector('#lap>div:last-of-type').textContent = getTimeString(); lapTime = Date.now(); // lap保持数を超えたら先頭の子要素を削除 if(document.querySelector('#lap').childElementCount > lapCount) document.querySelector('#lap').removeChild(document.querySelector('#lap').childNodes[0]); // スクロール位置を最下部に document.querySelector('#lap').scrollTop = document.querySelector('#lap').scrollHeight; setStorage(); } }, false); // カウント開始 function startCount() { const now = Date.now(); startTime = now - stopTime; lapTime = now - lapStopTime; return setInterval(printTime, 1); } // カウント停止 function stopCount() { const now = Date.now() stopTime = now - startTime; lapStopTime = now - lapTime; } // 経過時間表示 function printTime() { document.querySelector('#disp').textContent = getTimeString(); } function getTimeString() { const now = Date.now(), time = now - startTime, splitTime = now - lapTime, main = Math.floor(time / 3600000) + ':' + String(Math.floor(time / 60000) % 60).padStart(2, '0') + ':' + String(Math.floor(time / 1000) % 60).padStart(2, '0') + '.' + String(time % 1000).padStart(3, '0'), split = Math.floor(splitTime / 3600000) + ':' + String(Math.floor(splitTime / 60000) % 60).padStart(2, '0') + ':' + String(Math.floor(splitTime / 1000) % 60).padStart(2, '0') + '.' + String(splitTime % 1000).padStart(3, '0'); return main + ' / ' + split; } // localStorage保存 function setStorage() { localStorage.setItem('stopwatch_params', JSON.stringify({ state: state, startTime: startTime, stopTime: stopTime, lapTime: lapTime, lapStopTime: lapStopTime, })); } // localStorage削除 function deleteStorage() { localStorage.removeItem('stopwatch_params'); } // localStorage取得 function getStorage() { const params = localStorage.getItem('stopwatch_params'); return params ? JSON.parse(params) : {}; }追記
計測開始時に必要なパラメータをCookieに保存し、動作中にブラウザを閉じたりリロードしても復帰できるようにしました。
(ラップタイムの履歴は保存しません)
保存されたCookieは、計測停止時、または2日経過した時点で削除されます。追記2
動作時パラメータの保存先をCookieからlocalStorageに変更しました。
- 投稿日:2020-08-08T17:30:50+09:00
GASで毎朝その日の予定なんかを通知するLINE Bot - 1
この記事について
Qiita初投稿のうえ、プログラミング初心者です。至らぬ点がたくさんあるとは思いますが暖かく見守ってください…
Google Apps Scriptを用いて毎朝LINEでその日のいろいろを教えてくれるBotを作ったので解説も兼ねてご紹介します。この記事で取り扱う内容
- Google Apps Scriptの基本
- JavaScriptの基本~発展
- LINE Messaging API
- Flex Message
- Webスクレイピング(天気取得)
- Googleカレンダー(祝日取得)
- TimeTree API(予定取得)
実際どんな感じ?
表示するもの
- 日付
- 祝日
- 収集があるごみ
- 天気(最高気温)
- 予定(TimeTreeから)
GASプロジェクトを作成
こちらから新規のGoogle Apps Script プロジェクトを作成します。
アドレスバーに
script.new
で新規プロジェクトを作成できます。
.new ドメインを利用したショートカット一覧(英語)注意すること
V8 ランタイムを搭載したプロジェクトでのみ機能します(新規プロジェクトは最初から搭載しています)。
既存のプロジェクトを使う場合は、それがV8 ランタイムを搭載したプロジェクトであることを確認してください。
プロジェクトを開いた際にこのプロジェクトは Chrome V8 を搭載した新しい Apps Script ランタイムで実行しています。
という表示があれば問題ありません(詳細)。ええい、これが完成したコードだ!
(自分の拙いコードを公開するのは結構勇気が要りますね)
コード.jsfunction notify() { const garbage = [0, '資源ごみ', '可燃ごみ', 'カン・ビン', '不燃ごみ', '可燃ごみ', 'プラスチックごみ']; const area_code = 317; const d = new Date(); const today = (d.getMonth() + 1) + '月' + d.getDate() + '日(' + '日月火水木金土'[d.getDay()] + ')'; const [event_holiday] = CalendarApp.getCalendarById('ja.japanese#holiday@group.v.calendar.google.com').getEventsForDay(d); const holiday = event_holiday ? event_holiday.getTitle() : ''; let weather = "不明"; let temp = "?℃" const content = UrlFetchApp.fetch('https://www.jma.go.jp/jp/yoho/' + area_code + '.html').getContentText(); let pos = content.indexOf('<th class="weather">', content.indexOf('<th class="weather">') + 1); if (~pos) weather = content.substring(pos + 20, content.length); pos = weather.indexOf('"><br>\n</th>'); if (~pos) weather = weather.substring(0, pos); pos = weather.indexOf('alt='); if (~pos) weather = weather.substring(pos + 5, weather.length); const words = { '時々': '|', '一時': '|', '後': '»', '晴れ': '☀', '曇り': '☁', '雨': '☔', '雪': '⛄' }; for(let key in words) { weather = weather.replace(key, words[key]); } pos = content.indexOf('<td class="max">'); if (~pos) { temp = content.substring(pos + 16, content.length); temp = temp.substring(0, temp.indexOf('度')) + '℃'; } let flex = { 'type': 'bubble', 'size': 'giga', 'body': { 'type': 'box', 'layout': 'vertical', 'contents': [ { 'type': 'text', 'text': today, 'weight': 'bold', 'size': 'xxl', 'flex': 0 }, { 'type': 'box', 'layout': 'horizontal', 'contents': [ { 'type': 'filler' }, { 'type': 'text', 'text': weather, 'size': 'lg', 'color': '#444444', 'flex': 0, 'gravity': 'center' } ] }, { 'type': 'box', 'layout': 'horizontal', 'contents': [ { 'type': 'filler' }, { 'type': 'text', 'text': temp, 'size': 'lg', 'color': '#f44336', 'margin': 'xs', 'flex': 0, 'gravity': 'center' } ] }, { 'type': 'separator', 'margin': 'xl', 'color': '#808080' }, { 'type': 'box', 'layout': 'vertical', 'contents': [ //EVENTS ], 'margin': 'xl' } ] } }; if (holiday != '') { flex.body.contents[1].contents.splice(0, 0, { 'type': 'text', 'text': holiday, 'size': 'md', 'color': '#808080', 'flex': 0, 'gravity': 'center' }); } if (garbage[d.getDay()]) { let line = holiday == '' ? 1 : 2; flex.body.contents[line].contents.splice(0, 0, { 'type': 'text', 'text': garbage[d.getDay()], 'size': 'md', 'color': '#808080', 'flex': 0, 'gravity': 'center' }); } const props = PropertiesService.getScriptProperties(); const TIMETREE_TOKEN = props.getProperty('TIMETREE_TOKEN'); const opt = { 'headers': { 'Authorization': 'Bearer ' + TIMETREE_TOKEN }, 'method': 'get' }; const cals = JSON.parse(UrlFetchApp.fetch('https://timetreeapis.com/calendars', opt).getContentText()).data; const z = (t) => ('0' + t).slice(-2); let ev_exists = 0; for (let key in cals) { let cal = JSON.parse(UrlFetchApp .fetch('https://timetreeapis.com/calendars/' + cals[key].id + '/upcoming_events?timezone=Asia/Tokyo&days=1', opt) .getContentText()).data; for (let k in cal) { let {title, start_at, end_at, all_day} = cal[k].attributes; let start = new Date(start_at); let end = new Date(end_at); let time = all_day ? '終日' : z(start.getHours()) + ':' + z(start.getMinutes()) + '-' + z(end.getHours()) + ':' + z(end.getMinutes()); let schedule = { 'type': 'box', 'layout': 'horizontal', 'contents': [ { 'type': 'text', 'text': time, 'flex': 0, 'color': '#808080', 'gravity': 'center', 'size': 'md' }, { 'type': 'text', 'text': title, 'size': 'lg', 'weight': 'bold', 'color': '#606060', 'flex': 0, 'gravity': 'center', 'margin': 'lg' } ], 'margin': 'sm' }; flex.body.contents[4].contents.push(schedule); ev_exists |= 1; } } if (!ev_exists) { flex.body.contents.splice(3, 2); } const LINE_TOKEN = props.getProperty('LINE_TOKEN'); const payload = { 'messages': [ { 'type': 'flex', 'altText': today, 'contents': flex } ] }; const opt_line = { 'headers': { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': 'Bearer ' + LINE_TOKEN }, 'method': 'post', 'payload': JSON.stringify(payload) }; UrlFetchApp.fetch('https://api.line.me/v2/bot/message/broadcast', opt_line); }もちろん、これだけでは機能しません。
アクセストークンを取得し設定する必要があるのですが、長くなりましたので、手順は次の記事でご紹介します。
- 投稿日:2020-08-08T17:21:44+09:00
three.js オブジェクトがカメラに収まるように奥行きを自動調整させる
概要
three.jsに限らずですが、何らかの3Dオブジェクトをカメラにちょうど収めたいという場面があると思います。固定アスペクト比なら手動で調節しても良いですが、アスペクト比が変動する場合、物体がはみ出したり小さすぎたりしてしっくりこない場合があります。
なので、いい感じにオブジェクトが収まるようにカメラの奥行きを自動調節するものをthree.jsで実装してみました。デモ
以下が今回作ったデモです。
画面をドラッグすると物体が回転しますが、ちょうど直方体全体がカメラに収まるように調整されているのがわかると思います。https://arihide.github.io/demos/fit_camera/
ソースコードはこちら
https://github.com/Arihide/demos/tree/master/fit_camera解説
やりたいことを改めて説明すると、上の画像のように現在のカメラ位置ではスクリーンに収まらない場合に、カメラの奥行きを調節することで収まるようにします。これを2つの手順で実現します。
1. スクリーンの中心から一番遠い点を求める
2. スクリーンから一番遠い点が画面内に収まるように奥行きを調整する1. スクリーンの中心から一番遠い点を求める
物体すべてをカメラに収めるとは、言い換えると「その物体の中で一番スクリーンから遠い頂点を画面に収める」のと等しいです。
なので、ここではその頂点を求めたいワケですが、今回は泥臭く全ての頂点を1つ1つ射影して、どの点が最も遠いか求めています。{ // 1. スクリーン座標の中心からみて最も遠い点を求める let max = -Infinity, maxIndex = 0 let point = new THREE.Vector3() for (let i = 0; i < 8; i++) { point.set( i & 0b100 ? this._targetBox.max.x : this._targetBox.min.x, i & 0b010 ? this._targetBox.max.y : this._targetBox.min.y, i & 0b001 ? this._targetBox.max.z : this._targetBox.min.z, ).sub(this._targetCenter).project(this.camera) let pointMax = Math.max(point.x, point.y, -point.x, -point.y) if (pointMax >= max) { max = pointMax maxIndex = i } } farthestPoint.set( maxIndex & 0b100 ? this._targetBox.max.x : this._targetBox.min.x, maxIndex & 0b010 ? this._targetBox.max.y : this._targetBox.min.y, maxIndex & 0b001 ? this._targetBox.max.z : this._targetBox.min.z, ) }2.スクリーンから一番遠い点が画面内に収まるように奥行きを調整する
次に、先ほど求めた頂点をカメラに収める処理について説明しますが、説明のため視錐台をy軸方向から見た画像を載せます。
ここで、
- 座標軸原点は現在のカメラ位置
- $\theta$ はカメラの画角
- $(z, x)$ は先ほど求めたスクリーンから一番遠い点
です。
図からもわかるように$(z, x)$は描画領域に収まっていない(=スクリーンの外にある)です。
なので、ちょうど収まるようなカメラと点の距離 $z_{target}$ を求めます。正接の定義から以下が成り立ちます。
\tan (\theta / 2) = \frac{x}{z_{target}}これを次のように式変形します。
z_{target} = z \cdot \frac{x}{z \tan(\theta / 2)}ここで、$\frac{x}{z \tan(\theta / 2)} $は実はxをパースペクティブ射影変換した物と等価です。
(詳しくは完全ホワイトボックスなパースペクティブ射影変換行列を参照)
そこで、この射影変換をprojectと書くとすると、$z_{target}$ の導出式は次のように書けます。z_{target} = z \cdot project(x)
最後に
1. スクリーンの中心から一番遠い点を求める
2. スクリーンから一番遠い点が画面内に収まるように奥行きを調整する
の処理を実際に行っているソースコードを掲載します。
(CameraFitter.jsのfitCamera()関数部分)const targetToCamera = new THREE.Vector3() const farthestPoint = new THREE.Vector3() // スクリーン座標中心から最も遠い点 this.camera.lookAt(this._targetCenter) { // 1. スクリーン座標の中心からみて最も遠い点を求める let max = -Infinity, maxIndex = 0 let point = new THREE.Vector3() for (let i = 0; i < 8; i++) { point.set( i & 0b100 ? this._targetBox.max.x : this._targetBox.min.x, i & 0b010 ? this._targetBox.max.y : this._targetBox.min.y, i & 0b001 ? this._targetBox.max.z : this._targetBox.min.z, ).sub(this._targetCenter).project(this.camera) let pointMax = Math.max(point.x, point.y, -point.x, -point.y) if (pointMax >= max) { max = pointMax maxIndex = i } } farthestPoint.set( maxIndex & 0b100 ? this._targetBox.max.x : this._targetBox.min.x, maxIndex & 0b010 ? this._targetBox.max.y : this._targetBox.min.y, maxIndex & 0b001 ? this._targetBox.max.z : this._targetBox.min.z, ) } // 2.スクリーンから一番遠い点が画面内に収まるように奥行きを調整する // 直方体からカメラに向かう単位ベクトル targetToCamera.subVectors(this.camera.position, this._targetCenter).normalize() // 直方体→カメラベクトルに射影 const farthestPointProjected = new THREE.Vector3() farthestPointProjected.copy(farthestPoint).projectOnVector(targetToCamera) farthestPoint .sub(farthestPointProjected) // 原点を通るスクリーンに並行な面に移動する .project(this.camera) const scale = Math.max(farthestPoint.x, farthestPoint.y, -farthestPoint.x, -farthestPoint.y) // 結果を格納 this.spherical.setFromCartesianCoords( this.camera.position.x - this._targetCenter.x, this.camera.position.y - this._targetCenter.y, this.camera.position.z - this._targetCenter.z, ) this.spherical.radius = (this.spherical.radius * scale) + farthestPointProjected.length() // 球座標から元に戻す this.camera.position .setFromSpherical(this.spherical) .add(this._targetCenter)
- 投稿日:2020-08-08T17:18:15+09:00
Reactで作ったアプリをHerokuにデプロイする時にやること!!
こんにちは!
私は、作り終わったアプリをいつもHerokuにデプロイするのですが、大抵どっかでつまづいてしまうんですよね。
っていうことで、今日は、Reactでアプリを作った時のHerokuでのデプロイについて、私自身の実体験をもとに備忘録も兼ねてまとめてみます!1.Heroku(ヘロク)に登録
まずは、以下のサイトに行ってアカウント登録をします。
https://jp.heroku.com/今回は、登録の仕方の詳細は、端折らせて頂きます。
2.Create New Appする
それでは、引き続きHerokuで作業していきましょう。
下の画像のような、Newというボタンがあるので、クリックして、Create New Appします。
すると、App nameの入力を求められます。お好きな名前を付けてください。(ただし、大文字などいくつか使えない文字や記号もあります。)
もし、この部分を空欄にしてしまっても、Herokuがテキトーに名前を付けてくれるので大丈夫です!
Choose a regionはUnited StatesのままでOKです。
フォームの最後にあるCreate appボタンを押せば、これでアプリケーションをデプロイするための最初の準備ができました!3.アプリを作成する
ここでは、Reactを使って作りたいアプリを1つ作ってください。
むしろHello Worldでレンダリングするだけのページでも構いません。
作り方の解説は端折ります。4.さぁ、デプロイしてみましょう!
おかえりなさい!いよいよデプロイです!
ターミナルを開いて、以下のコマンドを入力してください。heroku loginq以外で好きなキーを押すように指示が表示される。好きなキーを押します。
すると、ブラウザが開いて、Log inと書かれたボタンが中央にあるのでクリック。
これで、ターミナルからHerokuへのログインができました。
それでは、作ったアプリを保存してあるフォルダに移動してください。cd 作ったアプリを保存してあるフォルダそして以下のコマンドを入力してください。
git init /* Reinitialized existing Git repository inと出てきます。 in以降は、GitHubのリポジトリ名とかになってます。*/ heroku git:remote -a Herokuで登録したまたは与えられたApp name /* set git remote heroku to っていうのが出てきます。 文字に色ついてたりしますが問題ないです。*/ git add . // addとピリオドの間に半角スペースを忘れない! git commit -am "make it better" /* 1 file changed, 2 insertions(+), 1 deletion(-)のような感じで、 変更したファイルの数が出てきます。何も変更していなければ、nothing to commit って出てきます。*/ git push heroku master少し時間がかかりますが、下にズラーと文字とか並びますが気長に待ちましょう。
5.ここから注意!エラー発生!?
Reactで作ったアプリをデプロイする時、git push heroku masterした後にエラーが起こりがちです。
! [remote rejected] master -> master (pre-receive hook declined) error: failed to push some refs to 'https://git.heroku.com/xxx.git'上記のエラーが出てくることがあります。(xxxはみなさんのHerokuでのApp nameです。)
ブラウザのHerokuに行って、Overviewページに移動します。そこでBuild faildと赤字で書かれているものが、ページ右半分の一番上に表記されているはずです。その項目の、View build logというリンクをクリックして見てみます。すると、buildで何が起こったのかを調べることができる「ログ」が書かれています。
その「ログ」の最後の方までスクロールしてください。以下のような文章が見つかると思います。Some possible problems: - Node version not specified in package.json https://devcenter.heroku.com/articles/nodejs-support#specifying-a-node-js-version - A module may be missing from 'dependencies' in package.json https://devcenter.heroku.com/articles/troubleshooting-node-deploys#ensure-you-aren-t-relying-on-untracked-dependencies /* 2つ目の、A moduleの文章は出てこない人もいると思います。 パッケージやライブラリの使用状況による!*/6.A module may be missing from 'dependencies' in package.jsonに対する解決策
エディタで、package.jsonを開き、上から4番目くらいにある"dependencies"という項目を見てください。そこできちんと使っているライブラリやパッケージの名前とバージョンの漏れがないか確認しましょう。このエラーが出ている人は、漏れがある可能性があるので、使っているのに書いていないパッケージなどがあれば、漏れなく追加しましょう。
7.Node version not specified in package.jsonに対する解決策
「ログ」の上の方を見てみると、lock fileをアップデートしてくださいって出てくるので、以下のコマンドを実行します。
npm install忘れないうちに、ブラウザ上のHerokuのSettingsページにある、Buildpacksに次のBuildpackを追加しておきます。
- heroku/nodejs
- https://github.com/mars/create-react-app-buildpack
Add buildpackというボタンをクリックして上記の2つを追加します。
heroku/nodejsというBuildpackは選択肢にあります。
2番目のBuildpackは、このようにURL表記で入力欄に入力してください。上記の工程を終了したら、もう一度、git add .の部分からデプロイに挑戦してみてください。
それでもダメな場合、package.jsonの"scripts"の"start"部分を次のように変更してください。
"scripts": { "start": "node index.js", // 左のように、node index.jsにする! "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" },以上です!!
長くなりましたが、お読み頂きありがとうございました!
不備等あれば、コメントでお知らせください!参考にさせて頂いた投稿
- 投稿日:2020-08-08T15:52:01+09:00
Day.jsでよく使う機能の覚書
JavaScriptの日付操作ライブラリであるDay.jsでよく利用する使い方について書き残しておきます。
公式ドキュメントが充実しているので、網羅的に知りたい方はそちらを見ていただいた方が早いでしょう。
基本
インストール
npm install dayjs --save // or yarn add dayjsimport
import dayjs from 'dayjs'; const now = dayjs() // 現在の日付情報を取得日付情報の取得
例として現在の日時から各種情報を取得します。
月が0から始まる値で取得されることに注意しましょう。
dayjs().year() // 年 dayjs().month() // 月 (0~11) dayjs().date() // 日 dayjs().day() // 曜日 0(日曜日)~6(土曜日) dayjs().format('YYYY/MM/DD') // フォーマットして表示日付情報の設定
dayjs().year(2020) // 日付情報の年を2020に更新 dayjs().month(8) // 日付情報の月を9月に更新 dayjs().date(12) // 日付情報の日を12日に更新 dayjs('2020-08-12') // 文字列をパースして更新その他
日付情報の加算
dayjs().add(3, 'year') // 年に3を加算 dayjs().add(3, 'month') // 月に3を加算 dayjs().add(3, 'week') // 週に3を加算日付情報の減算
dayjs().subtract(3, 'year') // 年に3を減算 dayjs().subtract(3, 'month') // 月に3を減算 dayjs().subtract(3, 'week') // 週に3を減算unixtimeで出力
dayjs().unix() dayjs().valueOf() // ミリ秒unixtimeunixtimeで日付情報の更新
dayjs.unix(1596836857) dayjs(1596869215397)Dateからの変換
const d = new Date(2020,8,12) const day = dayjs(d)
- 投稿日:2020-08-08T15:24:31+09:00
TypeScript 型エイリアス vs インターフェース
基本の違い
型エイリアス ・・・ クラスやオブジェクトの規格を定義
インターフェース ・・・ 型や型の組み合わせに別名を付ける型エイリアスtype Enemy = { hp: number mp: number }上記型エイリアスをインターフェースにすると、下記のようになります。
インターフェースインターフェースinterface Enemy = { hp: number mp: number }クラスやオブジェクトの規格はどちらでも定義可能ですが、
両者の違いは一体なんなのでしょうか?【違いその1】 型エイリアスは任意の型を指定できる!
型エイリアスは型の式(
|
や&
)を指定することができます。
インターフェースでは下記の記述はできません。型エイリアスtype A = number type B = A | string【違いその2】 拡張の記述法が異なる
正確に言うと、
extends
による拡張はinterfaceのみで使用でき、型エイリアスは【違いその1】の型の式を使用することでextendsと同じ機能を表現可能です。型エイリアスtype Animal = { name: string } type Dog = Animal & { bark: string }インターフェースinterface Animal = { name: string } interface Dog extends Animal { bark: string }【違いその3】 拡張の割り当てチェックの違い
【違いその2】に付随するものになりますが、 インターフェースの拡張は拡張元のインターフェースが、拡張先のインターフェースに割り当て可能かを確認します。
インターフェースinterface Base { ok: number | string ng: number } interface Ex extends Base { ok: number ng: string // 拡張元に割り当て不可能なのでここでエラーになる }型エイリアスでの交差
&
では、拡張元の型と拡張先の型を結合してくれます。型エイリアスtype Base = { ok: number } type Ex = Base & { ok: string | number } let Test: Ex = { ok: 333 // 結合され、 okは numberなので333が代入できる }【違いその4】 同名を複数宣言した場合にインターフェースはマージする
同じスコープ内で同名でinterfaceを宣言するとマージされます。
これは宣言のマージと言う機能で、型エイリアスにはない機能です。宣言のマージinterface Dog { name: string } interface Dog { bark: string } let dog: Dog = { name: 'ポチ', bark: 'ワンヌ' }参考
・Boris Cherny著『プログラミングTypeScript』(オライリー・ジャパン発行」
- 投稿日:2020-08-08T14:03:18+09:00
海を含めて日本を4色で塗り分ける
ソースコード
main.jsconst colors = ["red", "blue", "green", "yellow"]; const prefs = { 0: {name: "海", color: null, adjacentPrefCodes: [1, 2, 3, 4, 5, 6, 7, 8, 12, 13, 14, 15, 16, 17, 18, 22, 23, 24, 26, 27, 28, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]}, 1: {name: "北海道", color: null, adjacentPrefCodes: [0]}, 2: {name: "青森県", color: null, adjacentPrefCodes: [0, 3, 5]}, 3: {name: "岩手県", color: null, adjacentPrefCodes: [0, 2, 4, 5]}, 4: {name: "宮城県", color: null, adjacentPrefCodes: [0, 3, 5, 6, 7]}, 5: {name: "秋田県", color: null, adjacentPrefCodes: [0, 2, 3, 4, 6]}, 6: {name: "山形県", color: null, adjacentPrefCodes: [0, 4, 5, 7, 15]}, 7: {name: "福島県", color: null, adjacentPrefCodes: [0, 4, 6, 8, 9, 10, 15]}, 8: {name: "茨城県", color: null, adjacentPrefCodes: [0, 7, 9, 11, 12]}, 9: {name: "栃木県", color: null, adjacentPrefCodes: [7, 8, 10, 11]}, 10: {name: "群馬県", color: null, adjacentPrefCodes: [7, 9, 11, 15, 20]}, 11: {name: "埼玉県", color: null, adjacentPrefCodes: [8, 9, 10, 12, 13, 19, 20]}, 12: {name: "千葉県", color: null, adjacentPrefCodes: [0, 8, 11, 13]}, 13: {name: "東京都", color: null, adjacentPrefCodes: [0, 11, 12, 14, 19]}, 14: {name: "神奈川県", color: null, adjacentPrefCodes: [0, 13, 19, 22]}, 15: {name: "新潟県", color: null, adjacentPrefCodes: [0, 6, 7, 10, 16, 20]}, 16: {name: "富山県", color: null, adjacentPrefCodes: [0, 15, 17, 20, 21]}, 17: {name: "石川県", color: null, adjacentPrefCodes: [0, 16, 18, 21]}, 18: {name: "福井県", color: null, adjacentPrefCodes: [0, 17, 21, 25, 26]}, 19: {name: "山梨県", color: null, adjacentPrefCodes: [11, 13, 14, 20, 22]}, 20: {name: "長野県", color: null, adjacentPrefCodes: [10, 11, 15, 16, 19, 21, 22, 23]}, 21: {name: "岐阜県", color: null, adjacentPrefCodes: [16, 17, 18, 20, 23, 24, 25]}, 22: {name: "静岡県", color: null, adjacentPrefCodes: [0, 14, 19, 20, 23]}, 23: {name: "愛知県", color: null, adjacentPrefCodes: [0, 20, 21, 22, 24]}, 24: {name: "三重県", color: null, adjacentPrefCodes: [0, 21, 23, 25, 26, 29, 30]}, 25: {name: "滋賀県", color: null, adjacentPrefCodes: [18, 21, 24, 26]}, 26: {name: "京都府", color: null, adjacentPrefCodes: [0, 18, 24, 25, 27, 28, 29]}, 27: {name: "大阪府", color: null, adjacentPrefCodes: [0, 26, 28, 29, 30]}, 28: {name: "兵庫県", color: null, adjacentPrefCodes: [0, 26, 27, 31, 33]}, 29: {name: "奈良県", color: null, adjacentPrefCodes: [24, 26, 27, 30]}, 30: {name: "和歌山県", color: null, adjacentPrefCodes: [0, 24, 27, 29]}, 31: {name: "鳥取県", color: null, adjacentPrefCodes: [0, 28, 32, 33, 34]}, 32: {name: "島根県", color: null, adjacentPrefCodes: [0, 31, 34, 35]}, 33: {name: "岡山県", color: null, adjacentPrefCodes: [0, 28, 31, 34]}, 34: {name: "広島県", color: null, adjacentPrefCodes: [0, 31, 32, 33, 35]}, 35: {name: "山口県", color: null, adjacentPrefCodes: [0, 32, 34]}, 36: {name: "徳島県", color: null, adjacentPrefCodes: [0, 37, 38, 39]}, 37: {name: "香川県", color: null, adjacentPrefCodes: [0, 36, 38]}, 38: {name: "愛媛県", color: null, adjacentPrefCodes: [0, 36, 37, 39]}, 39: {name: "高知県", color: null, adjacentPrefCodes: [0, 36, 38]}, 40: {name: "福岡県", color: null, adjacentPrefCodes: [0, 41, 43, 44]}, 41: {name: "佐賀県", color: null, adjacentPrefCodes: [0, 40, 42]}, 42: {name: "長崎県", color: null, adjacentPrefCodes: [0, 41]}, 43: {name: "熊本県", color: null, adjacentPrefCodes: [0, 40, 44, 45, 46]}, 44: {name: "大分県", color: null, adjacentPrefCodes: [0, 40, 43, 45]}, 45: {name: "宮崎県", color: null, adjacentPrefCodes: [0, 43, 44, 46]}, 46: {name: "鹿児島県", color: null, adjacentPrefCodes: [0, 43, 45]}, 47: {name: "沖縄県", color: null, adjacentPrefCodes: [0]} }; function paintColor(pref) { // 既に自分に色が塗られている場合、処理を行わずにtrueを返す if (pref.color != null) return true for (let i = 0; i < colors.length; i++) { let color = colors[i]; pref.color = color; // 隣接する県と色が重複しているかチェック for (let i = 0; i < pref.adjacentPrefCodes.length; i++) { // 隣接する県と色が重複している場合、自分の色を消す let code = pref.adjacentPrefCodes[i]; if (color === prefs[code].color) { pref.color = null; break; } } // 自分に色が塗られている状態の場合(= 隣接県と色が重複しなかった場合)、隣接する県に色を塗る if (pref.color != null) { for (let i = 0; i < pref.adjacentPrefCodes.length; i++) { let code = pref.adjacentPrefCodes[i]; let isSuccess = paintColor(prefs[code]); // 隣接する県に色を塗れなかった場合、自分の色を消す if (isSuccess === false) { pref.color = null; break; } } // 隣接する県に色を塗れた場合、trueを返して処理を終える if (pref.color != null) { return true; } } } // ここまでたどり着くときは、自分に色を塗れなかった場合のみ return false; } paintColor(prefs[0]); for (const code in prefs) { console.log(String(code) + "," + prefs[code].name + "," + prefs[code].color); }実行
0,海,red 1,北海道,blue 2,青森県,blue 3,岩手県,green 4,宮城県,blue 5,秋田県,yellow 6,山形県,green 7,福島県,yellow 8,茨城県,blue 9,栃木県,red 10,群馬県,green 11,埼玉県,yellow 12,千葉県,green 13,東京都,blue 14,神奈川県,yellow 15,新潟県,blue 16,富山県,green 17,石川県,blue 18,福井県,green 19,山梨県,green 20,長野県,red 21,岐阜県,yellow 22,静岡県,blue 23,愛知県,green 24,三重県,blue 25,滋賀県,red 26,京都府,yellow 27,大阪府,blue 28,兵庫県,green 29,奈良県,red 30,和歌山県,green 31,鳥取県,blue 32,島根県,yellow 33,岡山県,yellow 34,広島県,green 35,山口県,blue 36,徳島県,blue 37,香川県,green 38,愛媛県,yellow 39,高知県,green 40,福岡県,blue 41,佐賀県,green 42,長崎県,blue 43,熊本県,green 44,大分県,yellow 45,宮崎県,blue 46,鹿児島県,yellow 47,沖縄県,blueExcelで確認してみる
参考資料
- 投稿日:2020-08-08T14:03:18+09:00
海含む日本を4色で塗り分ける
ソースコード
main.jslet colors = ["red", "blue", "green", "yellow"]; let prefs = { 0: {name: "海", color: null, adjacentPrefCodes: [1, 2, 3, 4, 5, 6, 7, 8, 12, 13, 14, 15, 16, 17, 18, 22, 23, 24, 26, 27, 28, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]}, 1: {name: "北海道", color: null, adjacentPrefCodes: [0]}, 2: {name: "青森県", color: null, adjacentPrefCodes: [0, 3, 5]}, 3: {name: "岩手県", color: null, adjacentPrefCodes: [0, 2, 4, 5]}, 4: {name: "宮城県", color: null, adjacentPrefCodes: [0, 3, 5, 6, 7]}, 5: {name: "秋田県", color: null, adjacentPrefCodes: [0, 2, 3, 4, 6]}, 6: {name: "山形県", color: null, adjacentPrefCodes: [0, 4, 5, 7, 15]}, 7: {name: "福島県", color: null, adjacentPrefCodes: [0, 4, 6, 8, 9, 10, 15]}, 8: {name: "茨城県", color: null, adjacentPrefCodes: [0, 7, 9, 11, 12]}, 9: {name: "栃木県", color: null, adjacentPrefCodes: [7, 8, 10, 11]}, 10: {name: "群馬県", color: null, adjacentPrefCodes: [7, 9, 11, 15, 20]}, 11: {name: "埼玉県", color: null, adjacentPrefCodes: [8, 9, 10, 12, 13, 19, 20]}, 12: {name: "千葉県", color: null, adjacentPrefCodes: [0, 8, 11, 13]}, 13: {name: "東京都", color: null, adjacentPrefCodes: [0, 11, 12, 14, 19]}, 14: {name: "神奈川県", color: null, adjacentPrefCodes: [0, 13, 19, 22]}, 15: {name: "新潟県", color: null, adjacentPrefCodes: [0, 6, 7, 10, 16, 20]}, 16: {name: "富山県", color: null, adjacentPrefCodes: [0, 15, 17, 20, 21]}, 17: {name: "石川県", color: null, adjacentPrefCodes: [0, 16, 18, 21]}, 18: {name: "福井県", color: null, adjacentPrefCodes: [0, 17, 21, 25, 26]}, 19: {name: "山梨県", color: null, adjacentPrefCodes: [11, 13, 14, 20, 22]}, 20: {name: "長野県", color: null, adjacentPrefCodes: [10, 11, 15, 16, 19, 21, 22, 23]}, 21: {name: "岐阜県", color: null, adjacentPrefCodes: [16, 17, 18, 20, 23, 24, 25]}, 22: {name: "静岡県", color: null, adjacentPrefCodes: [0, 14, 19, 20, 23]}, 23: {name: "愛知県", color: null, adjacentPrefCodes: [0, 20, 21, 22, 24]}, 24: {name: "三重県", color: null, adjacentPrefCodes: [0, 21, 23, 25, 26, 29, 30]}, 25: {name: "滋賀県", color: null, adjacentPrefCodes: [18, 21, 24, 26]}, 26: {name: "京都府", color: null, adjacentPrefCodes: [0, 18, 24, 25, 27, 28, 29]}, 27: {name: "大阪府", color: null, adjacentPrefCodes: [0, 26, 28, 29, 30]}, 28: {name: "兵庫県", color: null, adjacentPrefCodes: [0, 26, 27, 31, 33]}, 29: {name: "奈良県", color: null, adjacentPrefCodes: [24, 26, 27, 30]}, 30: {name: "和歌山県", color: null, adjacentPrefCodes: [0, 24, 27, 29]}, 31: {name: "鳥取県", color: null, adjacentPrefCodes: [0, 28, 32, 33, 34]}, 32: {name: "島根県", color: null, adjacentPrefCodes: [0, 31, 34, 35]}, 33: {name: "岡山県", color: null, adjacentPrefCodes: [0, 28, 31, 34]}, 34: {name: "広島県", color: null, adjacentPrefCodes: [0, 31, 32, 33, 35]}, 35: {name: "山口県", color: null, adjacentPrefCodes: [0, 32, 34]}, 36: {name: "徳島県", color: null, adjacentPrefCodes: [0, 37, 38, 39]}, 37: {name: "香川県", color: null, adjacentPrefCodes: [0, 36, 38]}, 38: {name: "愛媛県", color: null, adjacentPrefCodes: [0, 36, 37, 39]}, 39: {name: "高知県", color: null, adjacentPrefCodes: [0, 36, 38]}, 40: {name: "福岡県", color: null, adjacentPrefCodes: [0, 41, 43, 44]}, 41: {name: "佐賀県", color: null, adjacentPrefCodes: [0, 40, 42]}, 42: {name: "長崎県", color: null, adjacentPrefCodes: [0, 41]}, 43: {name: "熊本県", color: null, adjacentPrefCodes: [0, 40, 44, 45, 46]}, 44: {name: "大分県", color: null, adjacentPrefCodes: [0, 40, 43, 45]}, 45: {name: "宮崎県", color: null, adjacentPrefCodes: [0, 43, 44, 46]}, 46: {name: "鹿児島県", color: null, adjacentPrefCodes: [0, 43, 45]}, 47: {name: "沖縄県", color: null, adjacentPrefCodes: [0]} }; function paintColor(pref) { // 既に自分に色が塗られている場合、処理を行わずにtrueを返す if (pref.color != null) return true for (let i = 0; i < colors.length; i++) { let color = colors[i]; pref.color = color; // 隣接する県と色が重複しているかチェック for (let i = 0; i < pref.adjacentPrefCodes.length; i++) { // 隣接する県と色が重複している場合、自分の色を消す let code = pref.adjacentPrefCodes[i]; if (color === prefs[code].color) { pref.color = null; break; } } // 自分に色が塗られている状態の場合(= 隣接県と色が重複しなかった場合)、隣接する県に色を塗る if (pref.color != null) { for (let i = 0; i < pref.adjacentPrefCodes.length; i++) { let code = pref.adjacentPrefCodes[i]; let isSuccess = paintColor(prefs[code]); // 隣接する県に色を塗れなかった場合、自分の色を消す if (isSuccess === false) { pref.color = null; break; } } // 隣接する県に色を塗れた場合、trueを返して処理を終える if (pref.color != null) { return true; } } } // ここまでたどり着くときは、自分に色を塗れなかった場合のみ return false; } paintColor(prefs[0]); for (const code in prefs) { console.log(String(code) + "," + prefs[code].name + "," + prefs[code].color); }実行
0,海,red 1,北海道,blue 2,青森県,blue 3,岩手県,green 4,宮城県,blue 5,秋田県,yellow 6,山形県,green 7,福島県,yellow 8,茨城県,blue 9,栃木県,red 10,群馬県,green 11,埼玉県,yellow 12,千葉県,green 13,東京都,blue 14,神奈川県,yellow 15,新潟県,blue 16,富山県,green 17,石川県,blue 18,福井県,green 19,山梨県,green 20,長野県,red 21,岐阜県,yellow 22,静岡県,blue 23,愛知県,green 24,三重県,blue 25,滋賀県,red 26,京都府,yellow 27,大阪府,blue 28,兵庫県,green 29,奈良県,red 30,和歌山県,green 31,鳥取県,blue 32,島根県,yellow 33,岡山県,yellow 34,広島県,green 35,山口県,blue 36,徳島県,blue 37,香川県,green 38,愛媛県,yellow 39,高知県,green 40,福岡県,blue 41,佐賀県,green 42,長崎県,blue 43,熊本県,green 44,大分県,yellow 45,宮崎県,blue 46,鹿児島県,yellow 47,沖縄県,blueExcelで確認してみる
参考資料
- 投稿日:2020-08-08T13:12:35+09:00
Node.js + JavaScript で HTTP リダイレクト先の URL を取得する
概要
- Node.js + JavaScript で HTTP リダイレクト先の URL を取得する
- 動作確認環境: Node.js 14.7.0 + macOS Catalina
ソースコード
get_redirect.js というファイル名で以下の内容を保存する。
get_redirect.js'use strict' const https = require('https') const http = require('http') // リダイレクト先 URL を取得する関数 function get_redirect_url(src_url) { return new Promise((resolve, reject) => { try { // https と http で使うモジュールを変える const client = src_url.startsWith('https') ? https : http // 4xx や 5xx ではエラーが発生しないので注意 client.get(src_url, (res) => { // HTTP レスポンスから Location ヘッダを取得 (ヘッダ名は小文字) resolve(res.headers['location']) }).on('error', (err) => { reject(err) }) } catch(err) { reject(err) } }) } (async () => { // コマンドライン引数を取得 const src_url = process.argv[2] // リダイレクト先URLを取得 const redirect_url = await get_redirect_url(src_url) .catch(err => { console.log(err) }) // リダイレクト先URLを出力 if (redirect_url) { console.log(redirect_url) } })()実行例。
$ node get_redirect.js https://bit.ly/3kmTOkc https://t.co/yITSBp4ino$ node get_redirect.js https://t.co/yITSBp4ino https://qiita.com/niwasawa$ node get_redirect.js https://qiita.com/niwasawa参考資料
- 投稿日:2020-08-08T12:55:08+09:00
【JavaScript】数値範囲による分岐
前置き
ふと、数値の範囲で分岐させるのって面倒だったなぁと思い返したはいいけど、どう書いていたがすぐ思い出せなかったので自分用に記載。
コードはよくありそうな年代別の分岐。
コード
const getGeneration = age => { if (!Number.isFinite(age)) return console.error('数値以外が入力'); if (age<0) return console.error('0歳未満?'); if (age<12) return console.log('12歳未満'); if (age<20) return console.log('12歳~19歳'); if (age<30) return console.log('20歳代'); if (age<40) return console.log('30歳代'); if (age<50) return console.log('40歳代'); if (age<60) return console.log('50歳代'); if (age<70) return console.log('60歳代'); if (age<80) return console.log('70歳代'); if (age<100) return console.log('80歳以上'); if (age<120) return console.log('100歳以上'); return console.log('120歳以上'); }; getGeneration(-1); // => 0歳未満? getGeneration('14'); // => 数値以外が入力 getGeneration(14); // => 12歳~19歳 getGeneration(200); // => 120歳以上Number.isFinite()の行は、数値以外の入力が絶対にない状態なら削除OK。
最後に
もっとスマートな書き方はないものか…
- 投稿日:2020-08-08T01:39:37+09:00
VisualforceでQRコード作成してみた その2
↑のGifは見づらいと思うので、こちらで動画を公開しています。今回は、前回の記事VisualforceでQRコード作成してみたの続編です。
取引先責任者の画面でボタンクリックで、表示されているレコードの添付ファイルに保存しております。
取引先責任者の関連リストに表示するために、↑のGifでは画面リロードを行っています。
処理の手順としては
1.JavaScriptでQRコード作成
2.QRコードのバイナリデータをApexに渡す
3.ApexでContentDocument作成
となっております。そのうちソースコードも公開すると思います。
使用事例
作ってみたはいいものの、良い使用事例が思い浮かびませんでした。
というのも、例えば「イベントの出席確認用のQRコードを作成するためには、出席者すべてのレコードページでボタンクリックをしなければならず実用的ではないと思ったからです。ただし、LWCのScan Barcodesと合わせて、Salesforceのみで実装を完結できるところに将来性を感じるような気がします。
良い使用事例が思いついた方は、コメント等頂けると幸いです。まとめ
他サービスと連携しなくても、Slaesforceだけで様々なことが出来るようになっていますね。次はLWCのScan Barcodesか上記の課題であるQRコード添付ファイルの一括作成に取り組んでみようかなと思います。
- 投稿日:2020-08-08T01:37:11+09:00
十字キーASCII入力
十字キーASCII入力
概要
十字キーでASCIIで文字入力する仕様とその実装。D-pad ASCII Input。
動機、目的、特徴
- 入力キーが少ないデバイスで入力できる
- 仕様がシンプル
- 実装がシンプル
- 実用的な速度で入力できる
- 画面を見なくても入力できる
- 画面を見れば覚えていなくても入力できる
仕様
- 十字キーを4回入力し、各2bitを合わせて1Byteにし、それをASCIIの文字コードとして文字入力する
- ↑、←、→、↓がそれぞれ0、1、2、3になる
- そのキーを押した場合の残り候補となる文字群が画面に表示される
- キャンセルキーを設けて2ビットずつ取り消すようにしてもよい
例
- Hello World
文字 入力 H ←↑→↑ e ←→←← l ←→↓↑ l ←→↓↑ o ←→↓↓ Space ↑→↑↑ W ←←←↓ o ←→↓↓ r ←↓↑→ l ←→↓↑ d ←→←↑ 実装例
JavaScriptで65行で実装した例。Escapeで2ビットずつキャンセルできる。
<!DOCTYPE html> <meta charset="utf-8" /> <div> <textarea class="text" style="height: 4em; width: 80em;" autofocus></textarea> </div> <div><input class="progress" /></div> <div class="candidate"></div> <script> function codeToStr(i) { if (i === 0x09) return "[Tab]"; if (i === 0x0a) return "[Enter]"; if (i === 0x20) return "[Space]"; if (i < 0x20 || 0x7f <= i) return ""; return String.fromCodePoint(i); } function updateCandidates(codeStart, codeLen) { let html = '<table style="font-family: monospace;">'; const rowCodeLen = codeLen / 4; for (let i = 0; i < 4; ++i) { const rowCodeStart = codeStart + rowCodeLen * i; html += "<tr>"; html += `<td>${"↑←→↓"[i]}</td>` html += `<td>0x${rowCodeStart.toString(16).padStart(2, "0")}</td>`; html += "<td>"; for (let j = 0; j < rowCodeLen; ++j) { const code = rowCodeStart + j; html += codeToStr(code); } html += "</td>"; html += "</tr>"; } html += "</table>"; document.querySelector(".candidate").innerHTML = html; } function updateProgress(code2bs) { const progress = code2bs.reduce((a, c) => a + "↑←→↓"[c], ""); document.querySelector(".progress").value = progress; } let code2bs = []; function onKeyDown(e) { const arrowKeys = ["ArrowUp", "ArrowLeft", "ArrowRight", "ArrowDown"]; const idx = arrowKeys.indexOf(e.key); if (idx >= 0) { code2bs.push(idx); e.preventDefault(); } else if (e.key === "Escape") { code2bs.pop(); e.preventDefault(); } else { code2bs = []; } updateProgress(code2bs); let code = code2bs.reduce((a, c, i) => a + (c << (2 * (3 - i))), 0); if (code2bs.length === 4) { document.querySelector(".text").value += String.fromCodePoint(code); code = 0; code2bs = []; } updateCandidates(code, 256 / (1 << (code2bs.length * 2))); } onload = () => { updateCandidates(0, 256); document.querySelector(".text").onkeydown = e => onKeyDown(e); }; </script>メモ
入力 分類 ↑→○○ 記号 ↑↓○○ 数字 ←↑○○ アルファベット大文字、前半 ←←○○ アルファベット大文字、後半 ←→○○ アルファベット小文字、前半 ←↓○○ アルファベット小文字、後半
- 投稿日:2020-08-08T01:37:11+09:00
十字キーASCII入
十字キーASCII入力
概要
十字キーでASCIIを入力する仕様とその実装。D-pad ASCII Input。
動機、目的、特徴
- 入力キーが少ないデバイスで入力できる
- 仕様がシンプル
- 実装がシンプル
- 実用的な速度で入力できる
- 画面を見なくても入力できる
- 画面を見れば覚えていなくても入力できる
仕様
- 十字キーを4回入力し、各2bitを合わせて1Byteにし、それをASCIIの文字コードとして文字入力する
- ↑、←、→、↓がそれぞれ0、1、2、3になる
- そのキーを押した場合の残り候補となる文字群が画面に表示される
- キャンセルキーを設けて2ビットずつ取り消すようにしてもよい
例
- Hello World
文字 入力 H ←↑→↑ e ←→←← l ←→↓↑ l ←→↓↑ o ←→↓↓ Space ↑→↑↑ W ←←←↓ o ←→↓↓ r ←↓↑→ l ←→↓↑ d ←→←↑ 実装例
JavaScriptで64行で実装した例。Escapeで2ビットずつキャンセルできる。
<!DOCTYPE html> <meta charset="utf-8" /> <div> <textarea class="text" style="height: 4em; width: 80em;" autofocus></textarea> </div> <div><input class="progress" /></div> <div class="candidate"></div> <script> function codeToStr(i) { if (i === 0x09) return "[Tab]"; if (i === 0x0a) return "[Enter]"; if (i === 0x20) return "[Space]"; if (i < 0x20 || 0x7f <= i) return ""; return String.fromCodePoint(i); } function updateCandidates(codeStart, codeLen) { let html = '<table style="font-family: monospace;">'; const rowCodeLen = codeLen / 4; for (let i = 0; i < 4; ++i) { const rowCodeStart = codeStart + rowCodeLen * i; html += "<tr>"; html += `<td>${"↑←→↓"[i]}</td>` html += `<td>0x${rowCodeStart.toString(16).padStart(2, "0")}</td>`; html += "<td>"; for (let j = 0; j < rowCodeLen; ++j) { const code = rowCodeStart + j; html += codeToStr(code); } html += "</td>"; html += "</tr>"; } html += "</table>"; document.querySelector(".candidate").innerHTML = html; } function updateProgress(code2bs) { const progress = code2bs.reduce((a, c) => a + "↑←→↓"[c], ""); document.querySelector(".progress").value = progress; } let code2bs = []; function onKeyDown(e) { const arrowKeys = ["ArrowUp", "ArrowLeft", "ArrowRight", "ArrowDown"]; const idx = arrowKeys.indexOf(e.key); if (idx >= 0) { code2bs.push(idx); e.preventDefault(); } if (e.key === "Escape") { code2bs.pop(); e.preventDefault(); } updateProgress(code2bs); let code = code2bs.reduce((a, c, i) => a + (c << (2 * (3 - i))), 0); if (code2bs.length === 4) { document.querySelector(".text").value += String.fromCodePoint(code); code = 0; code2bs = []; } updateCandidates(code, 256 / (1 << (code2bs.length * 2))); } onload = () => { updateCandidates(0, 256); document.querySelector(".text").onkeydown = e => onKeyDown(e); }; </script>メモ
入力 分類 ↑→○○ 記号 ↑↓○○ 数字 ←↑○○ アルファベット大文字、前半 ←←○○ アルファベット大文字、後半 ←→○○ アルファベット小文字、前半 ←↓○○ アルファベット小文字、後半
- 投稿日:2020-08-08T00:39:16+09:00
JS URLのクエリをオブジェクトに変換する
本文
JavaScriptでURLのクエリ部分をオブジェクトに変換するための関数群です。車輪の再発明だと思いますが毎回調べるので記録します。引数は型チェックしていません。
// URLからクエリ(?以降)を抜き出します。 function urlGetQuery(url) { const i = url.indexOf("?"); return (i != -1) ? url.substring(i + 1) : null; } // URLのクエリから「=」より前をキー、後を値に持つオブジェクトを作成します。 function createQueryObjectForUrlQuery(query) { return Object.fromEntries(query.split("&").map(s => s.split("="))); } // URLからクエリを抜き出して、クエリの「=」より前をキー、後を値に持つオブジェクトを作成します。 function createQueryObjectForUrl(url) { return createQueryObjectForUrlQuery(urlGetQuery(url)); }Google検索でYahooと調べた結果に適用すると次のようになります。
// Google検索でYahooを調べたときのYahoo!のURL(改変) const url = "https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&url=https%3A%2F%2Fwww.yahoo.co.jp%2F&usg=..."; // URLからクエリ部分のオブジェクトを作成 const query = createQueryObjectForUrl(url); // 全体を出力 console.log(query); //>> Object { sa: "t", rct: "j", q: "", esrc: "s", source: "web", url: "https%3A%2F%2Fwww.yahoo.co.jp%2F", usg: "..." } // urlをURIデコードして出力 console.log(decodeURIComponent(query.url)); //>> https://www.yahoo.co.jp/蛇足
Object.fromEntries
にキーと値の配列を与えることで対応するオブジェクトが作成できます。- URLをデコードする関数は
decodeURI
とdecodeURIComponent
があります。今回は「:」「/」のような特殊文字もデコードしたいのでdecodeURIComponent
を使用しています。