- 投稿日:2019-10-02T22:59:08+09:00
指定した画像の上にスタンプのせるやつ作った
はじめに
指定した画像の上にスタンプのせるサービスあるじゃないですか
勉強になるかと思ったので似たようなものを作ってみましたどんなやつ
url
https://nena3.github.io/gurasan_stamp/source
https://github.com/nena3/gurasan_stamp使っている技術
Vue.js, JavaScript, Canvas
サーバーはGithubPagesを使用仕様
画像をアップロード
↓
アプロドされた画像を表示
↓
アイコンをすきなとこに動かす
↓
合成した画像を出力
画像出力部分は
DOMを動かして最終的にCanvasでそれを再現するような仕様になっている詰まったところ
canvasのrotateがちょっと難しかった
参考
https://weblike-curtaincall.ssl-lolipop.jp/blog/?p=594こめんと
iphoneとか古いブラウザでは動かないかもしれない
あとめっちゃ使いづらいしサングラスのアイコンとか透けとるやないかい
サンタの帽子もフチが透けとるやないかい
おわりに
GithubPagesすげえ!
GithubPagesがあれば簡単なwebサービスはタダでサクサクつくれますね
- 投稿日:2019-10-02T22:36:11+09:00
JavaScript async/awaitに関して
async/awaitとは?
Promise
による非同期処理を簡潔に記述するための記法。- 非同期処理は
then
を繋げる形で記載もできるが、async/await
は可読性を向上させる。使い方
async
を付与したfunctionを用意
→async
を付与するとPromise
を返却し、return後にPromise
がresolveされる。
- 上記関数内の
Promise
処理部分の前にawait
を付与する。
→await
はPromise
の解決待ち、resolveされた値を取得する。function sugarPromise(){ return new Promise(function(resolve) => { setTimeout(() => {resolve('hogehoge2')}, 10000) }); } async function sugarSync(){ const result = await sugarPromise(); // sugarPromise()内でPromiseが解決するまで次の処理を行わない console.log(result); } sugarSync(); console.log('hogehoge1');実行すると
hogehoge1 hogehoge2となるはず。(1と2は10秒の間隔)
注意点
- 複数のawaitを記述するとそれぞれ待たないといけない。上記の例を以下の通り書き直すと10秒×3=30秒待たないといけない。
const result1 = await sugarPromise(); const result2 = await sugarPromise(); const result3 = await sugarPromise();次の通り記載すると並列で処理が可能。
const result1 = sugarPromise(); const result2 = sugarPromise(); const result3 = sugarPromise(); const x1 = await result1; const x2 = await result2; const x3 = await result3; console.log(x1);
- 投稿日:2019-10-02T22:35:45+09:00
RaspberryPiで監視カメラ 暗い部屋の撮影編
はじめに
前回、RaspberryPiで監視カメラを作成したのですが、暗い部屋でベビーモニタとして利用するには工夫が必要という状況になっていました。
暗い部屋で使うために
- USBライトの制御
- カメラの設定変更
について調査してみたので、まとめておきます。
USBライトの制御
ハードウェア
Can☆Doで買ったUSBライトを使用します。
RaspberryPiに差してもlsusbで認識されないので、クラスとして認識するような仕組みはなくUSBコネクタから電力供給しているだけの動作のようです。
たぶん、どのメーカのUSBライトも同じ仕様だと思います。
Linux側からUSBポートの電源供給を制御する方法を探してみます。hub-ctrl
ぐぐったら、hub-ctrlというソフトを使うことでUSBの制御ができるとのこと。
手順通りにgccでビルドしてインストールできました。
gcc -o hub-ctrl hub-ctrl.c -lusb以下のコマンドで点灯、消灯ができました。
sudo hub-ctrl -h 0 -P 2 -p 0 sudo hub-ctrl -h 0 -P 2 -p 1権限設定等
pythonからhub-ctrlを起動する場合にsudoのパスワード入力が問題になるので回避策を探します。
最初はsudoしなくてもUSB操作ができるグループを追加すればいけると思いましたが、そういうグループは無いんですね。
グループを追加はあきらめてsudoのパスワード要求をしないように設定を変更します。sudo visudo/usr/local/bin/hub-ctrl にコピーしたのでこれにNOPASSWDを指定した行を追加します
monitor_user ALL=NOPASSWD: /usr/local/bin/hub-ctrlこれでsudoでのパスワード要求されなくなりますので、pythonからの呼び出しに支障はなくなりました。
クライアント側
HTML
点灯制御するトリガーのボタンを用意します。
usb.html<html> <head> <script src="./usb.js"></script> </head> <body onload="on_load();"> <input type="button" value="usb" onclick="on_button_light();"> <div> <canvas id="canvas_image" width="640" height="480"></canvas> </div> </body> </html>javascript
ボタンのハンドラでUSBライトの点灯制御を要求するコマンドを送信します。
usb.jsvar image_socket = null; var usb_socket = null; var mode_usb = true; function on_load() { // 画像通信用WebSocket接続 img_url = "ws://" + location.hostname + ":60002" image_socket = new WebSocket(img_url); image_socket.binaryType = 'arraybuffer'; image_socket.onmessage = on_image_message; usb_url = "ws://" + location.hostname + ":60004" usb_socket = new WebSocket(usb_url); usb_socket.binaryType = 'arraybuffer'; } function on_button_light() { mode_usb = mode_usb ? false : true; var command_data = { mode: mode_usb }; usb_socket.send(JSON.stringify(command_data)); } function on_image_message(recv_data) { // 受信したデータをbase64文字列に変換 var recv_image_data = new Uint8Array(recv_data.data); var base64_data = "" for (var i=0; i < recv_image_data.length; i++) { base64_data += String.fromCharCode(recv_image_data[i]); } // 画像をcanvasに描画 var canvas_image = document.getElementById('canvas_image'); var ctx = canvas_image.getContext('2d'); var image = new Image(); image.onload = function() { ctx.drawImage(image, 0, 0); } image.src = 'data:image/jpeg;base64,' + window.btoa(base64_data); }サーバー側
USBの電源制御するWebSocketのサーバーを用意します。
USB制御サーバー
クライアント側で送信したON/OFFの要求を受けてhub-ctrlを呼び出します。
UsbServer.pyimport asyncio import websockets import json import subprocess class UsbServer: def __init__(self, loop, address, port): self.loop = loop self.address = address self.port = port async def _handler(self, websocket, path): while True: try: recv_data = await websocket.recv() dic_data = json.loads(recv_data) except: print('usb recv Error.') break if dic_data['mode']: command = ['sudo', 'hub-ctrl', '-h', '0', '-P', '2', '-p', '0'] else: command = ['sudo', 'hub-ctrl', '-h', '0', '-P', '2', '-p', '1'] print(command) try: process = subprocess.Popen(command, stdout=subprocess.PIPE) output = process.stdout.read().decode('utf-8') print(output) except: print('usb proc Error.') def run(self): self._server = websockets.serve(self._handler, self.address, self.port) self.loop.run_until_complete(self._server) self.loop.run_forever() if __name__ == '__main__': loop = asyncio.get_event_loop() wss = UsbServer(loop, '0.0.0.0', 60004) wss.run()USBの親HUBごと電源をOFFにするのでUSB AudioもOFFになるのは問題ですが、電源制御対応の子USB HUBを挟めば対応できるそうです。(手持ちのHUBでは動作確認できませんでした)
カメラの設定変更
USBライトの明かりだけはうまく撮影できなかったので、カメラ側の設定もいじることにします。
PiCameraで変更できるプロパティで明るさを制御します。
明るく撮影する設定
前回作成したImageサーバーにフレームレート、シャッタースピード、露出補正の設定を追加します。
公式の説明を読んでいろいろ設定してみましたが、バージョンで動作が違うので正しい設定がよくわかりませんでした。ネットの情報もばらばらで正解がよくわからないです。試行錯誤してうまくいった設定値にしています。
ImageServer.pyimport asyncio import websockets import io from picamera import PiCamera class ImageServer: def __init__(self, loop, address , port): self.loop = loop self.address = address self.port = port self.camera = PiCamera() self.camera.framerate = 1 self.camera.exposure_compensation = 10 self.camera.shutter_speed = 1000 * 8000 async def _handler(self, websocket, path): with io.BytesIO() as stream: for _ in self.camera.capture_continuous(stream, format='jpeg', use_video_port=True, resize=(640,480)): stream.seek(0) try: await websocket.send(stream.read()) except: print('image send Error.') break stream.seek(0) stream.truncate() def run(self): self._server = websockets.serve(self._handler, self.address, self.port) self.loop.run_until_complete(self._server) self.loop.run_forever() if __name__ == '__main__': loop = asyncio.get_event_loop() ws_is = ImageServer(loop, '0.0.0.0', 60002) ws_is.run()動作確認
シャッタースピードが遅いのでライトのON/OFFしてから少し待たないと更新されないですが、それなりに撮影できています。
おわりに
USBの電源制御とシャッタースピードの制御でなんとか実用レベルになりました。
- 投稿日:2019-10-02T22:10:36+09:00
Raspberry Pi で最新鋭のユーロ地図を作る話
欧州 1:1,000,000 地図のオープンデータ EuroGlobalMap を最新鋭のウェブ地図にする、しかも Raspberry Pi 一台で、という話を以下のリンク先にまとめました。
https://hackmd.io/@hfu/inazo-steps
次のバージョンは、概ね1ヶ月後を目当てに作ろうと思います。以上、国連ベクトルタイルツールキット(UNVT)でした。
- 投稿日:2019-10-02T21:49:45+09:00
Javascriptでスライドショー作ってみたぉ。
..html
<!DOCTYPE html>
<meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link rel="stylesheet" href="css/styles.css"><div class="container"> <main> <img> </main> <nav> <ul> <li id="play">PLAY</li> <li id="prev"><</li> <li id="next">></li> </ul> </nav> <ul class="thumbnails"> </ul> </div>
..javascript
'use strict';
{
const images = [ 'img/pic00.png', 'img/pic01.png', 'img/pic02.png', 'img/pic03.png', 'img/pic04.png', 'img/pic05.png', 'img/pic06.png', 'img/pic07.png', ]; //今何番目の画像を表示しているかという変数 let currentNum = 0; // サムネイル画像をクリックしたときの関数処理 function setMainImage(value){ document.querySelector('main img').src = value; } // メインのイメージのソースをimages配列のcurrentNum番目とする // document.querySelector('main img').src = images[currentNum]; setMainImage(images[currentNum]); // currentクラスを外す独自の関数 function removeCurrentClass(){ document.querySelectorAll('.thumbnails li')[currentNum].classList.remove('current'); } function addCurrentClass(){ document.querySelectorAll('.thumbnails li')[currentNum].classList.add('current'); } // thumbnailsクラスの取得 const thumbnails = document.querySelector('.thumbnails'); // サムネイル画像たちの生成 images.forEach((value, index) => { const li = document.createElement('li'); if(index === currentNum){ li.classList.add('current'); } li.addEventListener('click', () => { setMainImage(value); removeCurrentClass(); currentNum = index; addCurrentClass(); }); const img = document.createElement('img'); img.src = value; li.appendChild(img); thumbnails.appendChild(li); }); const next = document.getElementById('next'); next.addEventListener('click', () => { removeCurrentClass(); currentNum++; if(currentNum === images.length){ currentNum = 0; } addCurrentClass(); setMainImage(images[currentNum]); }); const prev = document.getElementById('prev'); prev.addEventListener('click', () => { removeCurrentClass(); currentNum--; if(currentNum < 0){ currentNum = images.length - 1; } addCurrentClass(); setMainImage(images[currentNum]); });}
..css
.container{
width: 300px;
margin: 0 auto;
user-select: none;
}ul{
list-style: none;
padding: 0;
margin: 0;
}main{
margin-bottom: 8px;
}
main img{
width: 300px;
height: 200px;
vertical-align: bottom;
}
nav{
margin-bottom: 8px;
}
nav ul{
display: flex;
justify-content: space-between;
}
nav li {
width: 60px;
border: 1px solid #dedede;
border-radius: 4px;
font-size: 12px;
padding: 4px;
text-align: center;
cursor: pointer;
user-select: none;
}
nav li:hover{
background: #f8f8f8;
}play{
width: 140px;}
.thumbnails{
display: grid;
grid-template-columns: repeat(5, 56px);
gap: 5px;
}
.thumbnails li {
cursor: pointer;
opacity: 0.4;
}
.thumbnails li:hover{
opacity: 1;
}
.thumbnails li.current{
opacity: 1;
}
.thumbnails img{
width: 56px;
vertical-align: bottom;
}
- 投稿日:2019-10-02T20:05:31+09:00
javascript非同期処理~Promise,async,awaitとコールバック関数~
きっかけ
仕事で
vue.js
を使うことになって非同期処理について勉強していたところ、Promise
とかasync
とかawait
とかで?????となったのでふんわりな理解だったコールバック関数について学び直そうと決意現状の私の理解
コールバック関数
は関数に入れる関数。それ以外は...非同期処理でやれることの例
- ボタンがクリックされた後に何かを表示する
- ファイルの読み込みが完了した後に何かをする
非同期処理のイメージ
※こちらのイメージは拾い画なのですが拾い元が特定できなくてリンクを載せることができません。。。知ってる方いましたら教えて...
関数を実行するときと値として扱う際の書き方の違い
- カッコ【()】をつけるかつけないか
hoge.jsconst max1 = Math.max(1, 2); // これはカッコがついてるので関数の計算結果が入る const max2 = Math.max; // これはカッコがないので関数自体が入る
- カッコなしを実行しようとすると
not a function
エラーが発生する相手に実行してもらうのがコールバック関数
- 以下で考えるとconsole.logがコールバック関数となる。
huga.jssetTimeout(function() { console.log('Hello!'); }, 2000);関数を受け取る関数を高階関数と呼ぶ
- さっきのコードで言うと
setTimeout
が高階関数。非同期処理の書き方
- 1.非同期処理関数はコールバック関数を受け取る高階関数にする
- 2.利用者は「終わったら実行したい処理」をコールバック関数として渡す
- 3.非同期処理関数は処理が終わったらコールバック関数を呼び出す
DOMのイベントで非同期処理
- クリックイベントにコールバック関数(console.log)を紐付け
piyo.jsdocument.querySelector('.my-button').addEventListener('click', function(event) { console.log('clicked!'); });- 関数名でも紐付け可能
puyo.jsfunction callback(event) { console.log('Hello'!); } document.querySelector('.my-button').addEventListener('click', callback);非同期処理を記述するPromiseの基本
- 今までの内容で非同期処理には高階関数とコールバック関数が必要なことがわかったと思うが、処理が多くなるとネストが多段になって読みにくくなってしまう。
- そこで
Promise
と言う仕組みを使うことで簡潔に記述できる- 使い方とサンプルは以下
- 1.まずPromiseをnewすることによりPromiseオブジェクトを作成。
- 2.Promiseのコンストラクタには、実行したい処理を書いた関数を渡し、処理が済んだらresolve関数を呼び出すことで終了を明示する。
- 3.Promiseオブジェクトのthenメソッドに、Promise終了後に処理したい関数を渡す。
- 4.「Promiseの実行が済んだ後にxxxする」
promise.jsconst promise = new Promise((resolve, reject) => { setTimeout(() => { console.log('hello');//実行したい処理 resolve();//実行したい処理が終わったことを明示。thenメソッドへ。 }, 500); }); promise.then(() => console.log('world!'));Promise〜値を渡す1〜
- resolveには値を渡すこともできる
- resolve関数に渡した値は、thenメソッドで受け取ることができる。
- 以下サンプル(XHRのレスポンスをresolve関数に渡している)
resolve.jsconst promise = new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', 'foo.txt'); xhr.addEventListener('load', (e) => resolve(xhr.responseText)); xhr.send(); }); promise.then((response) => console.log(response));Promise〜値を渡す2〜
reject
はresolve
同様、値を渡すことができるreject
が呼ばれたらcatch
が実行されてエラー終了させるreject.jsconst promise = new Promise((resolve, reject) => reject('error')); promise.then(() => console.log('done')) // thenは実行されない .catch((e) => console.log(e)); // 「error」とだけ表示されるthenを繋げる
thenチェーン
!!!
- これがPromiseのいいところみたいでthenを繋げて書くことができる。
then.jsfunction printAsync(text, delay) { const p = new Promise((resolve, reject) => { setTimeout(() => { console.log(text); resolve(); }, delay) }); return p; } printAsync('hello', 500) //500ミリ秒ごとに「hello」「world」「lorem」「ipsum」と表示する .then(() => printAsync('world', 500)) .then(() => printAsync('lorem', 500)) .then(() => printAsync('ipsum', 500));Promiseを使わない時と使った時のソース比較サンプル(ファイルオープン)
- 使わないとき
nothen.jsfunction openFile(url, onload) { const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.addEventListener('load', (e) => onload(e, xhr)); xhr.send(); } openFile('foo.txt', (event, xhr) => { openFile('bar.txt', (event, xhr) => { openFile('baz.txt', (event, xhr) => { console.log('done!'); }); }); });
- 使った時
then.jsfunction openFile(url) { const p = new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.addEventListener('load', (e) => resolve(xhr)); xhr.send(); }); return p; } openFile('foo.txt') .then((xhr) => openFile('bar.txt')) .then((xhr) => openFile('baz.txt')) .then((xhr) => console.log('done!'));複数のPromiseの終了を待つPromise.all
- 上記と同じ処理を書き換えてみる
- 配列で渡してthenは1個で書ける。
- thenの結果は配列で返ってくるため注意
promise.allfunction openFile(url) { const p = new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.addEventListener('load', (e) => resolve(xhr)); xhr.send(); }); return p; } const promise = Promise.all([openFile('foo.txt'), openFile('bar.txt'), openFile('baz.txt')]); promise.then((xhrArray) => console.log('done!'))async関数とawait
やっと来ました。これについて知りたくて遡りしてたら結構知ってないといけないことが多かった。。。
awaitはPromiseを同期的に展開する(ように見せかける)機能。
Promiseオブジェクトを返す関数を呼び出す前につけると取得を待ってから次の処理へ行ってくれる
awaitは仕様条件があり、asyncがついた関数の中でしか利用できないと言う条件がある。
さっきのファイルオープンの処理をasync/awaitを使って書くと以下のようになる
async.jsfunction openFile(url) { const p = new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.addEventListener('load', (e) => resolve(xhr)); xhr.send(); }); return p; } async function loadAllFiles() { //ファイルオープンを待って次の処理へ const xhr1 = await openFile('foo.txt'); //ファイルオープンを待って次の処理へ const xhr2 = await openFile('bar.txt'); //ファイルオープンを待って次の処理へ const xhr3 = await openFile('baz.txt'); console.log('done!'); } loadAllFiles();async/awaitのエラー検出
- try-catchが書けるのでその中に書いていく
async.jsfunction openFile(url) { const p = new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.addEventListener('load', (e) => resolve(xhr)); xhr.send(); }); return p; } async function loadAllFiles() { try { //ファイルオープンを待って次の処理へ const xhr1 = await openFile('foo.txt'); //ファイルオープンを待って次の処理へ const xhr2 = await openFile('bar.txt'); //ファイルオープンを待って次の処理へ const xhr3 = await openFile('baz.txt'); console.log('done!'); } catch(error) { const { status, statusText } = error.response; console.log(`Error! HTTP Status: ${status} ${statusText}`); } } } loadAllFiles();あとは書いて覚える!!!!!
- 投稿日:2019-10-02T18:35:57+09:00
フォトショになりたい ver1. トーンカーブ
フォトショになりたい
- 画像加工系の機能を一つづつ実装するだけ
概要
- トーンカーブ
- LUT
- 露出
- ガンマ
- 両対数スケール
- コントラスト
注: この記事はPython3 & OpenCV で画像処理を学ぶ[3] 〜 トーンカーブ と LUT を理解する実装実験と内容が鬼ほど被っています
トーンカーブ
要するに$[0, 255] \rightarrow [0, 255]$の関数
LUT
全ピクセルに毎回演算してたら重すぎてたまったもんじゃない
→$[0, 255] \rightarrow [0, 255]$で、基本的には整数値なのだから256×256の表があればいい
→LUT(Look Up Table)
トーンカーブ編集系のエフェクトは、全て「LUTをいじる→LUTに従って画像を変換する」とすると良い
editLUT.jsfunction editLUT(img) { const lut = new Array(256); for (let i = 0; i < 256; i += 1) { let val = hoge(i); //ここでエフェクトをかける lut[i] = val; } LUT(img, lut); return img; }露出
k倍するだけ
f(v) = kvただ、kが1を超える時に明るいところがクリップするので対処する
exposure.jsfunction exposure(x, t) { return Math.min(x * t, 255); }ガンマ
正規化してγ乗するだけ
f(v) = v^\gamma(詳しくはガンマ補正のうんちくを参照)
gammaFunc.jsconst gammaFunc = (val, gamma) => 255 * Math.pow(val / 255, 1 / gamma);両対数スケール
ガンマよりも両端に関して滑らからしい
(詳しくはスケール対数曲線をトーンカーブとする画像補正を参照)f(v) = \frac{{\rm log}(1+Cv)}{{\rm log}(1+C)}\\逆関数:
f^{-1}(v) = \frac{e^{v{\rm log}(1+C)}-1}{C}log_log_scale.jsconst log_log_scale = (v, C) => C == 0 ? v : C > 0 ? 255*Math.log(1+v/255*C)/Math.log(1+C) : -255*(Math.exp(v/255*Math.log(1-C))-1)/Cコントラスト
暗いところをより暗く、明るいところをより明るく→S字カーブ
基本的にはシグモイドでいい
(詳しくはシグモイド関数でコントラスト強調を参照)でも定義域が有界じゃないので少し修正してもいい
(詳しい議論はSigmoid曲線の定義域と値域を[0,1]にしたいだけを参照)pseudoSigmoid.jsconst pseudoSigmoid (x, t, MAX) => // Maxは255とか t === 0 ? x : t > 0 ? (Math.asinh((2 * x / MAX - 1) * Math.sinh(t)) / 2 / t + 0.5) * MAX : (Math.sinh(t * (2 * x / MAX - 1)) / 2 / Math.sinh(t) + 0.5) * MAX;中心の値をいじりたければシグモイド関数でコントラスト強調の実装が良さそう
まとめ
- トーンカーブはLUTを使って実装する
- この辺りの関数は後に他でも用いるので覚えておく
- 投稿日:2019-10-02T18:26:16+09:00
CypressとResamble.jsを使ったwebサイトの差分比較
VueやReactなどを使って運用していると色んな所に影響するようなコンポーネントをさわるケースが出てくると思います。
そのときに影響範囲がある画面をポチポチして確認するのがしんどいので
予め確認したいページを指定してキャプチャを取れる状態にしておき差分があった場合に、画像で出力してくれる仕組みを作ってみました。
手順
- Cypressで比較したい画像のキャプチャを撮っておく
- 1で撮ったキャプチャと比較したい画像のキャプチャを撮る
- Resamble.jsで1と2の画像を比較して差分がある場合、差分画像を出力する
Cypressでキャプチャを撮る
Cypressで下記のようにページごとに、キャプチャを撮って現時点の日付で保存しておきます。(/e2e/screenshot/内の指定のフォルダに保存されます。)
画面サイズごとにキャプチャを取りたい場合は、それぞれ画面ごとに保存したいフォルダをかえて保存しておきます。
画面サイズを変更する場合は
cy.viewport
を指定します。https://github.com/kamem/clover.blue2/blob/master/test/diff/index.js
/test/e2e/integration/screenshot.jsimport moment from 'moment' const host = 'http://localhost:1341/' const pages = [ { name: 'top', path: '' }, { name: 'about', path: 'about' } ] pages.forEach(({ name, path }) => { context(name, () => { beforeEach(() => { cy.visit(`${host}${path}`) }) it(`${name} screenshot`, () => { cy.screenshot(`${name}/normal/${moment().format()}`) }) }) }) pages.forEach(({ name, path }) => { context(`small_${name}`, () => { beforeEach(() => { cy.viewport(320, 480) cy.visit(`${host}${path}`) }) it(`${name} screenshot`, () => { cy.screenshot(`${name}/small/${moment().format()}`) }) }) })Resamble.jsを使って差分画像の出力
/e2e/screenshot/内のCypressで保存したファイルをフォルダ単位で検索して。
そのフォルダ内の最新の2件を比較する仕組みにしました。package.jsonに下記を記述して、nodeでResemble.jsで画像の差分を出力を動作するようにしました。
package.js"scripts": { "diff": "node test/diff/" }コードはざっくりなので参考程度に...
https://github.com/kamem/clover.blue2/blob/master/test/e2e/integration/screenshot.js
/test/diff/index.jsconst fs = require('fs') const path = require('path') const compareImages = require('resemblejs/compareImages') const _ = require('lodash') const mkdirp = require('mkdirp') const getDiff = async (img1, img2, output = './output.png', options = {}) => { const data = await compareImages( fs.readFileSync(img1, () => {}), fs.readFileSync(img2, () => {}), options ) if (data.misMatchPercentage >= 0.01) { mkdirp(path.dirname(output), err => { if (err) return console.error(err) fs.writeFile(output, data.getBuffer(), () => {}) }) } } const searchDir = dir => { return fs .readdirSync(dir) .filter(item => !fs.existsSync(item)) .map(item => { const filePath = `${dir}/${item}` return { dir, item: fs.statSync(filePath).isDirectory() ? searchDir(filePath) : item } }) } const diffPngFiles = dirObj => { const pngFiles = getDiffFiles(dirObj) if (pngFiles.length === 2) { getDiff( `${pngFiles[0].dir}/${pngFiles[0].item}`, `${pngFiles[1].dir}/${pngFiles[1].item}`, `${pngFiles[0].dir.replace('e2e', 'diff')}.png` ) } dirObj.forEach(({ dir, item }) => { if (Array.isArray(item)) { diffPngFiles(item) } }) } const getDiffFiles = dirObj => { const pngFiles = dirObj.filter(({ item }) => ~item.indexOf('png')) return _.sortBy(pngFiles, ({ dir, item }) => { return -fs.statSync(`${dir}/${item}`).ctimeMs }).slice(0, 2) } diffPngFiles(searchDir('./test/e2e/screenshots'))「すごい頑張って差分テストを!」って感じではないですが。
一旦サクッと差分ポイントが確認できるーぐらいで使ってみるのはいいかなという感じです。今後もこのあたりもちょっとうまい方法ないかなという部分は考えて行きたいです。
- 投稿日:2019-10-02T18:26:16+09:00
CypressとResamble.jsを使ったwebサイトの差分画像比較
VueやReactなどを使って運用していると色んな所に影響するようなコンポーネントをさわるケースが出てくると思います。
そのときに影響範囲がある画面をポチポチして確認するのがしんどいので
予め確認したいページを指定してキャプチャを取れる状態にしておき差分があった場合に、画像で出力してくれる仕組みを作ってみました。
手順
- Cypressで比較したい画像のキャプチャを撮っておく
- 1で撮ったキャプチャと比較したい画像のキャプチャを撮る
- Resamble.jsで1と2の画像を比較して差分がある場合、差分画像を出力する
Cypressでキャプチャを撮る
Cypressで下記のようにページごとに、キャプチャを撮って現時点の日付で保存しておきます。(/e2e/screenshot/内の指定のフォルダに保存されます。)
画面サイズごとにキャプチャを取りたい場合は、それぞれ画面ごとに保存したいフォルダをかえて保存しておきます。
画面サイズを変更する場合は
cy.viewport
を指定します。https://github.com/kamem/clover.blue2/blob/master/test/diff/index.js
/test/e2e/integration/screenshot.jsimport moment from 'moment' const host = 'http://localhost:1341/' const pages = [ { name: 'top', path: '' }, { name: 'about', path: 'about' } ] pages.forEach(({ name, path }) => { context(name, () => { beforeEach(() => { cy.visit(`${host}${path}`) }) it(`${name} screenshot`, () => { cy.screenshot(`${name}/normal/${moment().format()}`) }) }) }) pages.forEach(({ name, path }) => { context(`small_${name}`, () => { beforeEach(() => { cy.viewport(320, 480) cy.visit(`${host}${path}`) }) it(`${name} screenshot`, () => { cy.screenshot(`${name}/small/${moment().format()}`) }) }) })Resamble.jsを使って差分画像の出力
/e2e/screenshot/内のCypressで保存したファイルをフォルダ単位で検索して。
そのフォルダ内の最新の2件を比較する仕組みにしました。package.jsonに下記を記述して、nodeでResemble.jsで画像の差分を出力を動作するようにしました。
package.js"scripts": { "diff": "node test/diff/" }コードはざっくりなので参考程度に...
https://github.com/kamem/clover.blue2/blob/master/test/e2e/integration/screenshot.js
/test/diff/index.jsconst fs = require('fs') const path = require('path') const compareImages = require('resemblejs/compareImages') const _ = require('lodash') const mkdirp = require('mkdirp') const getDiff = async (img1, img2, output = './output.png', options = {}) => { const data = await compareImages( fs.readFileSync(img1, () => {}), fs.readFileSync(img2, () => {}), options ) if (data.misMatchPercentage >= 0.01) { mkdirp(path.dirname(output), err => { if (err) return console.error(err) fs.writeFile(output, data.getBuffer(), () => {}) }) } } const searchDir = dir => { return fs .readdirSync(dir) .filter(item => !fs.existsSync(item)) .map(item => { const filePath = `${dir}/${item}` return { dir, item: fs.statSync(filePath).isDirectory() ? searchDir(filePath) : item } }) } const diffPngFiles = dirObj => { const pngFiles = getDiffFiles(dirObj) if (pngFiles.length === 2) { getDiff( `${pngFiles[0].dir}/${pngFiles[0].item}`, `${pngFiles[1].dir}/${pngFiles[1].item}`, `${pngFiles[0].dir.replace('e2e', 'diff')}.png` ) } dirObj.forEach(({ dir, item }) => { if (Array.isArray(item)) { diffPngFiles(item) } }) } const getDiffFiles = dirObj => { const pngFiles = dirObj.filter(({ item }) => ~item.indexOf('png')) return _.sortBy(pngFiles, ({ dir, item }) => { return -fs.statSync(`${dir}/${item}`).ctimeMs }).slice(0, 2) } diffPngFiles(searchDir('./test/e2e/screenshots'))「すごい頑張って差分テストを!」って感じではないですが。
一旦サクッと差分ポイントが確認できるーぐらいで使ってみるのはいいかなという感じです。今後もこのあたりもちょっとうまい方法ないかなという部分は考えて行きたいです。
- 投稿日:2019-10-02T18:08:16+09:00
Intl.PluralRules で助数詞表現
プログラムで、英語で「3番目の値」「5番目の値」などと出力したい場合、「"the " + n.toString() + "th value"」のように書きますよね。 ただこの場合、「1th」(1stが正しい)、「23th」(23rdが正しい)、のようにおかしいパターンが生じてしまいます。 こういうとき、みなさんならどう解決します?
— くいなちゃん (@kuina_ch) September 28, 2019JavaScript では
Intl.PluralRules
を使うといいかと思います。const pr = new Intl.PluralRules('en-US', { type: 'ordinal' }); const or = { other:'th', one:'st', two:'nd', few:'rd' } const o = n => n + or[ pr.select(n) ]; o(1); //1st o(2); //2nd o(3); //3rd o(4); //4th o(11); //11th o(21); //21st参考:
Intl.PluralRules - JavaScript | MDN
cldr-numbers-full/numbers.json at master · unicode-cldr/cldr-numbers-full · GitHub
- 投稿日:2019-10-02T18:08:16+09:00
Intl.PluralRules で序数詞表現
プログラムで、英語で「3番目の値」「5番目の値」などと出力したい場合、「"the " + n.toString() + "th value"」のように書きますよね。 ただこの場合、「1th」(1stが正しい)、「23th」(23rdが正しい)、のようにおかしいパターンが生じてしまいます。 こういうとき、みなさんならどう解決します?
— くいなちゃん (@kuina_ch) September 28, 2019JavaScript では
Intl.PluralRules
を使うといいかと思います。
Intl.PluralRules.prototype.select
メソッドは、数値を引数に取り、言語に応じたその数値のカテゴリ("zero", "one", "two", "few", "many","other")を返します。たとえば英語では、1は"one"、11は"other"が返ってきます。「1は"st"だけど11は"th"」のような処理の部分を、「"one"は"st"、"other"は"th"」のように簡略化できます。
サンプルconst pr = new Intl.PluralRules('en-US', { type: 'ordinal' }); const or = { other:'th', one:'st', two:'nd', few:'rd' } const o = n => n + or[ pr.select(n) ]; o(1); //1st o(2); //2nd o(3); //3rd o(4); //4th o(11); //11th o(21); //21st参考:
Intl.PluralRules - JavaScript | MDN
cldr-numbers-full/numbers.json at master · unicode-cldr/cldr-numbers-full · GitHub
- 投稿日:2019-10-02T17:45:08+09:00
フィリピンIT Coding Boot Camp 6ヶ月コースの4ヶ月目で思うこと
現在、フィリピンのセブでもないマニラでもないどこかの涼しい雨ばかりの街でIT Boot Camp(6ヶ月)に参加した僕が感じていることやIT勉強したい、身に付けたいけど最善の方法って結局学校?それとも働いた方がいい?などの答えのヒントに少しでもなればなぁと思います。(あくまで全て一個人の僕の感想です)
そもそもIT Boot Campて何?
これは、知らない方もいると思いますが、このIT Boot Campはビリーズブートキャンプの仲間ではなく、アメリカやイギリスなどでも開催されているITの知識を詰め込む集中講座のようなものです!
IT Boot Campの特徴
-超短期集中
-勉強する範囲が6ヶ月コースなどになってくると結構広い。
-授業は全て英語
-毎日コード書く生活漬け
-(他は知りませんが)インターンシップ先を用意してくれる
...こんなところですかね。
とりあえずまとめると、6ヶ月と言う期間でコードを書きWebsiteを作れるように学習し、そしてクライアントを獲得できるまでに成長させよう!
みたいなノリのコースですね。実際、仕事を自分で得れるほどまでに成長するの?
ここが全員気なっているところですよね〜。
結論から言います。(これもただの僕の意見です)
ただ言われてるだけのことをこなして学校が終わり次第家に帰っていたら絶対無理です。
ただ誤解して欲しくないのは、別にコースを否定している訳ではなく、ただお金をいただいてオンライン上でクライアント様の希望を目に見える形にすると言うのはとても大変と言うことです。6ヶ月のコースでそれが完璧に身につくはずはありません。いくら学校に高い授業料を払っていても、自分で主体的に動かなければ生き残ってはいけません。それならいかない方がいい?
そう思いがちですが、ちょっと待ってください。
だからこそIT Boot Campでは、エンジニアとして一番大事なスキルである自分をアップデートする方法を学べるんです。
これは本当に日常生活でも役に立つんですが、まず知らない事をすぐに調べると言う情報の収集の癖がつきますし、
その情報収集の仕方やcオーディングでバグが出た際の解き方など応用がきくテクニックもひたすら教えてくれます。でも授業では、実際そのやり方を教えてくれるだけで自分のスキルとして落とし込むまでの時間は取ってくれないんですよ。
なので、終わった〜って家に帰ってしますと綺麗さっぱり忘れてまたぐるぐる同じことを学んで時間だけ過ぎて言ってしまいます。
要は、自分で学んだことをしっかりと整理しアウトプットできればそれなりに成果の上がるコースだと言うことです。自分なりのメリットとデメリット
メリット
・コーディングの基礎は身につく(HTML、CSS、JavaScriptなど)。
・マネタイズやクライアントの獲得の仕方まで一応体系的に教えてくれる。
・やる気がある人にとってはいいスピード感でどんどん新しい分野や言語を教えてくれる
・仕事上だと何回もきくことに抵抗があるが生徒の立場なら何回も納得がいくまで聞ける。
・授業はほぼどこでも英語なので英語が上達する
・エンジニアになりたいけど、何をどうやって始めたらいいの?って言う方には唯一お勧め!
デメリット
・アカデミックな内容も多いので実践で活かせないこともある。
・前回の記事でも書きましたが、やっぱり実際に企業に入ろうとすると現実的にたかだか数ヶ月しかコーディングかじってないと言う現実を痛感するので自分でアンテナをしっかりはってどの言語やどんなスキルが生きるかを模索し続けながら勉強していく必要がある事。
(これに関してはいいきっかけにもなるので、メリットでもありますね!)
・日本の案件は全くカリキュラム内では取ろうとしないので海外案件を狙うというハードルの高さが少しある。
・カルチャーギャップで苦しむ時がある(来てみて感じてみてください笑)最後に
未経験で企業に入っていきなりコーディングを詰め込まれても正直限界がありますが、そのあとの仕事は自分で取らなくても用意されていると言うことになります。
一方、こういったコースを受講しいいご縁にも恵まれ、フリーランスとして活躍できれば自分の仕事を選ベルし嫌なら断れます。
値段だって交渉できます。とにかく規制がありません。自由です。これは勧誘でもないし、IT Coding Boot Campというシステムへの悪口でもありません。
ただ、実際に足を踏み入れたことがない方や、少しでも興味がある方達がこの記事をみて『やっぱやーめた』とかなったり『背中を押せたり』できればなと思い本音で全て書いてあリます。
なので、なんか言っていることが矛盾していればこのコースのいい側面もあるけど嫌なところや不満も抱えながらコーディングに励んでいたということです笑以上、僕の体験談でした。
何かありましたらコメントください。
読んでいただきありがとうございました。
- 投稿日:2019-10-02T17:18:26+09:00
WEBの知識0の私がURLを贈る
動機
誕生日に手紙を贈るのは普通すぎると思って, ちょっと勉強中のHTMLやらを使ってURLを贈ることにしました.
なんかまずい部分あれば教えていただけると幸いです...
この記事は躓いたところとかのメモになります.あまりにも酷いコード全貌 : github
どんなものを作ったのか
生年月日いれて, 合致していたら次のページ
→ドット絵のGIFがお祝いする52thとかいう書き方あってるんかな...
※iphone向けでしか作っていません.
開発環境
・sublime
・xampp 7.3.9xamppを用いてlocalhostで作成画面を確認しながら作成しました.
絶対もっと楽な方法ある...ドット絵
piskelというサイトでドット絵作成.ドット絵描くのが非常に楽しかった.
スマホ用に
やさしいWebアプリ
を参考にしました.<meta name="viewport" content="initial-scale=1.0">でスマホ向け画面になってくれるみたいです.
画像の表示を横幅に合わせるために CSSで調整.
.gif img{ width:100%; }誕生日を入力してもらうフォーム
誕生月・日のセレクトボタンを作成.
<form> <select name="sel2"> <option value="#"></option> <script type="text/javascript"> for(var i=1;i<13;i++){ document.writeln("<option>" +i + "月</option>"); } </script> </select> <select name="sel3"> <option value="#"></option> <script type="text/javascript"> for(var i=1;i<32;i++){ document.writeln("<option>" +i + "日</option>"); } </script> </select> <input type=button value="決定" onClick="return check(sel2.value,sel3.value)"> </form>function check()では当人の誕生日が入力されたら次のページへ
という感じ.
違えばalertでます.スマホでの音声出力
audioタグでは, safariやChromeで音楽の自動演奏ができないと知った時にはびっくりしました.
こちらのサイトを参考に, 画面タップで音楽が流れるような仕様に.<audio autoplay loop id="audio"> <source src="happy_birthday_to_you_unpluged.mp3"> </audio> <script type="text/javascript"> document.addEventListener('click', audioPlay); function audioPlay() { document.getElementById('audio').play(); document.removeEventListener('click', audioPlay); } </script>最後に
こんな初歩的なものでも知識が0からだったので2~3日程度かかったきがする.
また何か作りたい...
- 投稿日:2019-10-02T16:45:06+09:00
JavaScript (文字列と数値)
JavaScriptとは
JavaScriptはWebに特化したプログラミング言語の1つで、動的なWebページの制作などに用いられます。
console.log()
()に入力された文字をコンソールに出力する。
文字列
出力させたいテキストをダブルクォーテーション(")で囲む。
console.log("〇〇");
文末には必ずセミコロン(;)を付ける。コメントアウト
コメントをみなされ、web上には表示されない。
どのような意味を持つコードであるかを記すメモに使われる。
文頭に「//」と書く。//「こんにちは」と出力する。 console.log("こんにちは");数値
文字列と違い、クォーテーションで囲まない。
計算することができる。
+ - * / 数値と記号はすべて半角で記述する。
(%を使うと、割ったときの余りを求められる。)//文字列を出力 console.log("3+2"); //数値を出力 console.log(3+2);文字列の連結
「+」記号を用いると、文字列同士を連結することができる。
「"天然"+"水"」とすると「"天然水"」
という一つの文字列になる。//文字列の連結 console.log("天然"+"水");
- 投稿日:2019-10-02T16:13:00+09:00
Cocos2d-html5でスプラッシュ画面時点から背景色を変える
cocos2d-html5で、スプラッシュ画面時点から背景色を変えたいと思いました。SPAから滑らかにCocos2dに移行しようと思ったのです。概ね当たり前の部分、つまり、html内・loading.jsでの色指定に関わる部分を弄れば何とかなるのですが、WebGLの時だけ、一瞬画面が黒くなります。
一瞬のことなので、後回しにしようとも思ったのですが、こういうのってちょっとしたことながら「すげえ挙動不安定アプリっぽい見た目」になるので、耐えきれず一応ソースを読みました。
過程はすっ飛ばして、
frameworks > cocos2d-html5 > cocos2d > core > renderer > RendererWebGL.js > _clearColor
に背景色を設定するしかない模様。
確かに、ライブラリの読み込み時に介入できる方法をcocos2dは提供していないので、これは困りものです。
少なくとも現バージョン3.17.2では、ライブラリのソースを書き換えざるを得ない感じ。提案としては、ライブラリ初期化時に、呼び出し元HTMLの背景色を覗いて、_clearColorのデフォルト値にする、といった仕様が嬉しいんではないでしょうか。誰かプルリクしてあげて。
ちなみに、背景色を明るい色にすると、これに限らず色々な障害に阻まれる感じです。まだそのせいだとは確証できていませんが、TransitionCrossFadeとか。Cocos2d-HTML5って、黒背景で使う人がほとんどなのかも。
以上。
- 投稿日:2019-10-02T15:59:08+09:00
【JavaScript】非同期処理を同期的にする
JavaScript(Node.js)でHTTPリクエストを送ったり、ファイルの読取を実装しようとすると、コールバックだらけになって、HTTPリクエストの後に行う処理が実装しづらかったりする。
その場合、async/awaitやPromiseを使って同期的にすることができる。async/await 入門(JavaScript) - Qiita
https://qiita.com/soarflat/items/1a9613e023200bbebcb3HTTPリクエストで取得したCSVファイルをパースするサンプル↓
サンプルconst https = require('https'); const parse = require('csv-parse'); const moment = require('moment'); (async function() { const csvdata = await getCsv(); const keepdata = await parseCsv(csvdata); fillEmptyRecord(keepdata); console.log('finish'); }()); function getCsv() { const url = 'https://www.mizuhobank.co.jp/market/csv/quote.csv'; // csvファイル取得 let csvdata = ''; return new Promise(function(resolve, reject) { https.get(url, function(res) { res.on('data', function(data) { csvdata += data; }); // getが終わった後 res.on('end', function() { resolve(csvdata); }); res.on('error', function() { reject(null); }); }); }); } function parseCsv(csvdata) { let keepdata = []; return new Promise(function(resolve, reject){ parse(csvdata, { skip_empty_lines : true ,from_line : 4 }) .on('readable', function() { // 略 }) .on('end', function() { resolve(keepdata); }); }); } function filterCsv(record) { let date = moment(record[0], 'YYYY/M/D'); let startDate = moment().subtract(30, 'days'); return date.isSameOrAfter(startDate); } function fillEmptyRecord(records) { // 中略 for (let record of records) { console.log(record); } }
- 投稿日:2019-10-02T15:59:08+09:00
【JavaScript】非同期処理を同期にしてコールバック地獄から抜け出す
概要
JavaScript(Node.js)でHTTPリクエストを送ったり、ファイルの読取を実装しようとすると、コールバックだらけになって、HTTPリクエストの後に行う処理が実装しづらかったりする。
その場合、async/awaitやPromiseを使って同期的にすることができる。基礎知識については、下記ページを参照。async/await 入門(JavaScript) - Qiita
以下、Webサイトからcsvファイルを取得し、パースする処理を例にする。
コールバックで実装した場合
HTTPリクエストに
https
、csvのパースにcsv-parse
を使う。
どちらもコールバック関数を使用する設計になっているため、ひたすら関数の呼び出しの連鎖が続き、処理の全体が把握しづらくなる。コールバックを使用したサンプルconst https = require('https'); const parse = require('csv-parse'); const moment = require('moment'); (function() { getCsv(); // ※ HTTPリクエストもcsvの処理も非同期なので、真っ先に出力されてしまうログ console.log('finish'); }()); function getCsv() { // csvファイル取得 const url = 'https://www.mizuhobank.co.jp/market/csv/quote.csv'; let csvdata = ''; https.get(url, function(res) { res.on('data', function(data) { csvdata += data; }); res.on('end', function() { // 次の処理を呼び出す parseCsv(csvdata); }); }); } function parseCsv(csvdata) { // csvファイルを配列へ変換する let keepdata = []; parse(csvdata, { skip_empty_lines : true ,from_line : 4 }) .on('readable', function() { let record = null; while (record = this.read()) { if (filterCsv(record)) { keepdata.push(record); } } }) .on('end', function() { // 次の処理を呼び出す fillEmptyRecord(keepdata); }); } function fillEmptyRecord(records) { // 休日など、空いている日付を翌日のレートで埋める let size = records.length; let index = size - 1; let newRecords = []; while (index > 0) { let date = moment(records[index][0], 'YYYY/M/D'); let prevDate = moment(records[index - 1][0], 'YYYY/M/D'); let diff = date.diff(prevDate, 'days'); newRecords.splice(0, 0, records[index]); while (diff > 1) { let cloned = records[index].slice(0, records[index].length); date = date.subtract(1, 'days'); cloned[0] = date.format('YYYY/M/D'); newRecords.splice(0, 0, cloned); diff -= 1; } index -= 1; } // ここが処理の一番最後 for (let record of records) { console.log(record); } }Promiseを使用した場合
先ほどの例では分かりづらい…という問題を解消するために、async/await や Promise を使用する。
こうするとエントリポイントとなる関数に処理の流れを表すことができるため、流れが把握しやすくなるし、実装もしやすい。サンプルconst https = require('https'); const parse = require('csv-parse'); const moment = require('moment'); (async function() { const csvdata = await getCsv(); const keepdata = await parseCsv(csvdata); fillEmptyRecord(keepdata); // ちゃんと最後に出力される console.log('finish'); }()); function getCsv() { // csvファイル取得 const url = 'https://www.mizuhobank.co.jp/market/csv/quote.csv'; let csvdata = ''; return new Promise(function(resolve, reject) { https.get(url, function(res) { res.on('data', function(data) { csvdata += data; }); // getが終わった後 res.on('end', function() { resolve(csvdata); }); res.on('error', function() { reject(null); }); }); }); } function parseCsv(csvdata) { let keepdata = []; return new Promise(function(resolve, reject){ parse(csvdata, { skip_empty_lines : true ,from_line : 4 }) .on('readable', function() { // 略 }) .on('end', function() { resolve(keepdata); }); }); } function fillEmptyRecord(records) { // 中略 for (let record of records) { console.log(record); } }
- 投稿日:2019-10-02T15:50:01+09:00
Cordovaで音声入力する。
UIWebViewではnavigator.mediaDevicesが取得できず、audio inputが出来ない。
cordova-plugin-audioinputを使う。導入
cordova plugin add cordova-plugin-audioinput@1.0.1サンプルコードが現時点で最新の1.0.2では動かなかったため、1.0.1を入れた。
Blob形式でデータを取得する流れ
- 公式デモソースが教えてくれる。
https://github.com/edimuj/cordova-plugin-audioinput/blob/master/demo/wav-demo.js
Permissionを取る
準備
document.getElementById("startCapture").addEventListener("click", startCapture); // 送られてくるデータを貯める var onDeviceReady = function () { if (window.cordova && window.audioinput) { initUIEvents(); consoleMessage("Use 'Start Capture' to begin..."); // Subscribe to audioinput events // window.addEventListener('audioinput', onAudioInputCapture, false); window.addEventListener('audioinputerror', onAudioInputError, false); } else { consoleMessage("cordova-plugin-audioinput not found!"); disableAllButtons(); } };onAudioInput内部で、音声ストリーミングを取得する
開始
audioinput.start({audioSourceType: 0})終了
//停止 audioinput.stop(); // waveへ変換する consoleMessage("Encoding WAV..."); var encoder = new WavAudioEncoder(captureCfg.sampleRate, captureCfg.channels); encoder.encode([audioDataBuffer]); consoleMessage("Encoding WAV finished"); var blob = encoder.finish("audio/wav");
- 投稿日:2019-10-02T15:27:36+09:00
owl carousel2 ことはじめ
'owl carousel2' とは
カルーセルデザイン(スライドデザイン)を表現するライブラリのひとつです
特徴としては
- レスポンシブ対応
- ドラッグでのスライド移動ができる
- スワイプでのスライド移動ができる
といった、スマートデバイス向けの挙動をサポートしています
同様のライブラリに slick など、いくつかのライブラリが存在します
Download and Install
前提
- 必ずHTMLにjQueryをロードするようにしてください
- また、以下に記すタグ埋め込みは必ずjQueryのものの下に記載するようにしてください
- この記事を記述するに際して、使用しているライブラリやアプリのバージョンは以下の通りです
- jQuery
- 3.4.1
- owl carousel2 本体
- 2.3.4
- chrome
- 77.0 (77.0.3865.90 official build) (x64)
- IE
- 11 (11.356.18362.0)
公式サイトから
こちらの
Download
からzipをダウンロードできますzipを展開し、cssは
dist/assets
内の
- owl.carousel.min.css
- owl.theme.default.css
を任意のスタイルシートディレクトリにコピーし、javascriptは
dist
内の
- owl.carousel.min.js
を任意のスクリプトディレクトリにコピーしてください
CDNから
CDN がこちらからあるのでこれの
- owl.carousel.min.css
- owl.theme.default.css
- owl.carousel.min.js
をそれぞれ
<link>
タグ、<script>
タグに追加してくださいnpm / yarn
あまり詳しく解説しません
使用法 / 例
part1
一番シンプルなデザインについて記載します
HTML
<body> <div class="your_awesome_carousel owl-carousel owl-theme"> <div> <img src="your_awesome_image1.png"> </div> <div> <img src="your_awesome_image2.png"> </div> <div> <img src="your_awesome_image3.png"> </div> <div> <img src="your_awesome_image4.png"> </div> <div> <img src="your_awesome_image5.png"> </div> <div> <img src="your_awesome_image6.png"> </div> </div> </body>ハマりポイント
<div class="owl-carousel owl-theme">
の部分について
- この部分はカルーセルデザイン適用対象のrootになります
- 必ず
owl-carousel
owl-theme
の2つを設定する必要があります備考
- カルーセルデザインを適用させたい箇所のタグを全て
<div>
にしていますが、実際は<ul><li></li>...</ul>
のようなリストタグでも良いようですCSS (SCSS)
特に設定しなくても動いてくれます
javascript (jQuery)
$(document).ready(function () { $('.your_awesome_carousel').owlCarousel(); });EXAMPLE
https://codepen.io/nao-a/pen/gOYVLVe
part2
アイテムを中央にし、スクロールボタンが表示されているデザインを説明します
HTML
<body> <div> <ul class="owl-carousel owl-theme"> <li> <img src="your_awesome_image1.png"> </li> <li> <img src="your_awesome_image2.png"> </li> <li> <img src="your_awesome_image3.png"> </li> <li> <img src="your_awesome_image4.png"> </li> <li> <img src="your_awesome_image5.png"> </li> <li> <img src="your_awesome_image6.png"> </li> </ul> <div class="owl-prev"></div> <div class="owl-next"></div> </div> </body>一番の変化は最後から3~4行目にある
owl-prev
owl-next
のクラスを持つ<div>
タグになりますこれらがprev/nextのボタンに変化してくれます
CSS (SCSS)
.owl-item { opacity: 0.3; transition: 1s ease-out; } .owl-item.center { opacity: 1; } .owl-next { margin: 0 100px !important; } .owl-prev { margin: 0 100px !important; }.owl-item { opacity: 0.3; transition: 1s ease-out; &.center { opacity: 1; } } .owl { &-next { margin: 0 100px !important; } &-prev { margin: 0 100px !important; } }.owl-item & .owl-item.center
中央に来ているスライド以外を透過しています
owl-item
及びcenter
はowl carousel2が実行時に自動的にセットしてくれるクラスになりますowl-next/owl-prev
無理矢理 左右のマージンをあけています
!important
をつけないとmargin
属性が効いてくれませんでしたしかし、もしかしたらほかの方法で設定することができるかもしれません
javascript (jQuery)
$(document).ready(function () { $('.owl-carousel').owlCarousel({ center: true, items: 3, loop: true, margin: 10, nav: true, responsive: { 600: { items: 1 } } }); });EXAMPLE
https://codepen.io/nao-a/pen/pozMReo
最後に
どのライブラリでも言えることですが、触る為のルールを会得すれば、簡単にカルーセルデザインを実装することができる良いライブラリであると感じます
しかし、slickと比較した際に、slickにはある
beforeChange
のイベントコールバックの設定に丁度当たるものが無い為、スライドした瞬間にcenter
に来ている要素に対してスタイルを当てる、といったことは苦手な印象を受けましたさらっと触っただけですので、こちらももしかしたら別の方法によって達成可能かもしれません
よろしければご指摘くださいませ
- 投稿日:2019-10-02T14:50:33+09:00
JavaScript 覚えておくべき注意点!(1) JavaScriptで扱うデータ型 / 特殊文字の表現方法 / 変数にデータを記憶する方法 / 例
JavaScriptで扱うデータ型は7種類
※データ系とは? 「数値」のデータなら掛けたり割ったりすることができるが、「文字列」を掛けたり割ったりは出来ない。このようにデータにはいくつかの種類があります。
- 文字列(String)-----------'Hello'、"こんにちは"などの文字列
- 数値(Number)-----------100、15.23、-12.5などの数値
- 真偽値(Boolean)---------true、falseの2値で真と偽を表わす
- シンボル(Symbol)-------インスタンスが固有で不変となるデータ型
- null-----------------------null値を意味する特殊キーワード
- underfined---------------値が未定義であることを示す
- オブジェクト(Object)---上記6つのいずれでもない特殊なデータ型
主なエスケープシーケンス
※改行などの特殊文字はそのままでは文字列として表現できないために、バックスラッシュ「\n」で表現されます。この特殊文字のことをエスケープシーケンスと言います。
hensuu-data.js\n // ---------------------改行する \t // ---------------------タブを入れる \" // ---------------------""で囲まれた文中に"を入れる \' // ---------------------''で囲まれた文中に'を入れる \\ // ---------------------文字列中にバックスラッシュを入れる変数にデータを記憶する方法
- 代入演算子「=」を使う方法を「代入する」という。
- 「=」は「左辺の変数に右辺の値を記憶する」という意味である
data-kioku.js// 変数の宣言と利用 var name; // .....................変数宣言 name = 'Taro'; // ................nameに'Taro'を代入 console.log(name); // ............nameをconsole.logで表示 // 宣言と代入を同時にする例 var time = 60; // ................変数timeを宣言し、数値の60を代入 time = time * 60; // .............変数timeに「time * 60」の演算結果を代入 console.log(time); // ............3600例 BMI計算プログラムを作成しましょう
※入力 → 処理 → 出力
入力:体重・身長の値を得る
処理:体重と身長をもとにBMIの値を計算する
出力:計算結果をダイアログボックスで表示するこの流れでプログラムを組み立てる
reidai.js// 体重の数値を得る var weight; weight = prompt('BMIを測定します。まずはあなたの体重(kg)を入力してください'); // 身長の数値を得る var height; height = prompt('BMIを測定します。次にあなたの身長(m)を入力してください'); // 体重と身長からBMIを計算して、警告ダイアログに表示する var bmi = weight / (height * height); var message = 'あなたのBMIは「' + bmi + '」です。'; alert(message);
- 投稿日:2019-10-02T14:42:20+09:00
gatsby: command not found とエラーが出た時の対応
一般的なgatsbyのことはじめ
公式の手順通りにインストールしてくる
https://www.gatsbyjs.org/docs/quick-start
npm install -g gatsby-cli
gatsby: command not found と上手くいかない時がある
上記のコマンドではPCの設定などによっては上手くインストールできない場合があるようです。
そのためyarnを使用してインストールし直すと上手くいく。yarnのインストールはこちらから
https://yarnpkg.com/lang/en/docs/install/#mac-stable
sudo yarn global add gatsby-cli
上記のコマンドでやり直したところ上手くいきました。
- 投稿日:2019-10-02T12:26:10+09:00
WYSIWYGエディタをCKEditorで作ってみた
WYSIWYGエディタって何?
「What You See Is What You Get(見たままが得られる)」エディタ。うぃじうぃぐって読むらしけど誰が読めるんやろう。
ユーザー側からタグ付けとかスタイリングとかをさせることなく、
簡単に良い感じに文章が作れるエディターです。
イメージでいうとQiitaとかWordpressの投稿のエディタ画面みたいな感じです。これを簡単に実装できる方法を紹介します
CKEditor vs Draft.js
実装しようと思い、調べましたがものすごく種類あるみたいですね。
https://qiita.com/rana_kualu/items/fc752de7d4f2224b29ee最終2候補で悩みましたが、結局表題の通りCKEditorを使用しました。
理由はReact案件ではないことがメインです。(Reactが入ってるか否かで使い分けてもいいかも)CKEditor
2003年から存在する老舗の安定したWYSIWYGエディター。
高水準のプラグインが多く、プラグインをCKEditorに組み込むことでほとんどのニーズがカバーできそう。Draft.js
Facebook製で、単純な編集動作を備えたReactベースのWYSIWYGエディターコンポーネント
Reactを使ったプロジェクトなら相性良いと思う
参照:https://qiita.com/mottox2/items/9534f8efb4b09093a304パッケージ作成方法
早速作成方法についてです。
webpackでもcdnでも作成できますが、パッケージをダウンロードするのが個人的にはおすすめです。理由としては以下の通り!
- CKEditorのダウンロードページを通して直感的に好みのパッケージを作成できる。
- パッケージ内のコードを変更することで簡単にカスタマイズができる。
作成手順
https://ckeditor.com/ckeditor-4/download/
1. ダウンロードするパッケージの種類を選ぶ
中に入っているプラグインの数が変わります。カスタマイズしたいのであれば右のCustomizeを選択
2. プリセットの選択
3. プラグインの追加と削除
必要なプラグインを取捨選択します。右側から左側に必要なプラグインをドラッグ&ドロップで移動させます。(不要なプラグインは逆方向)
英語ですが簡単に説明もあり、詳細ページのリンクもあります。
4. 好きな見た目と、言語の選択
5. ダウンロード!
これで作成されたパッケージは以下の構成になっていると思います。
(Imageとか、要らないファイルとか多いです。必要に合わせて消しても良いかと思います)├── ckeditor │ ├── CHANGES.md │ ├── LICENSE.md │ ├── README.md │ ├── adapters │ │ └── jquery.js │ ├── build-config.js │ ├── ckeditor.js │ ├── config.js │ ├── contents.css │ ├── lang │ │ ├── ... │ ├── plugin.js │ ├── plugins │ │ ├──... │ │ │ │ ├── samples │ │ │ ├── skins │ │ └── ... │ ├── styles.js │ └── vendor │ └── promise.js使用方法
1. ファイル設置
先ほどの呼び出すHTMLと同階層に設置してください
(HTML側からのリンク変わるだけなので正直どこでも良いですが…)
2. HTML記述
<body> <div class="container"> <form id="editor"> <!-- textareaのクラス名にckeditorを指定するとcekditorに置き換わる --> <textarea class="ckeditor" name="editor"></textarea> <input id="editor__submit" type="submit" value="SEND"> </form> <div class="preview"></div> </div> <!-- ckeditor下のckeditor.jsを呼び出す --> <script src="../ckeditor/ckeditor.js"></script> <script src="./js/app.js"></script> </body>3. ckeditor編集
ckeditorの編集は
ckeditor/config.js
で行います。/** * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. * For licensing, see https://ckeditor.com/legal/ckeditor-oss-license */ CKEDITOR.editorConfig = function( config ) { // 必要なボタンはtoolbarGroupに追加 config.toolbarGroups = [ { name: 'document', groups: [ 'mode', 'document', 'doctools' ] }, { name: 'clipboard', groups: [ 'clipboard', 'undo' ] }, { name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] }, { name: 'forms' }, { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] }, { name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] }, { name: 'links' }, { name: 'insert' }, { name: 'styles' }, { name: 'colors' }, { name: 'tools' }, { name: 'others' }, { name: 'about' } ]; // 環境問わず表示を日本語にする config.language = 'ja'; // デフォルトのheightを変更 config.height = 300; };*注記
heightはデフォルトで200,widthは100%になっている
https://ckeditor.com/docs/ckeditor4/latest/features/size.htmlconfigで変えられる設定はこちら
https://ckeditor.com/docs/ckeditor4/latest/features/4. おまけ
変換後が見えるようにプレビュー画面を作成
(send押したらデータを下のプレビュー画面に反映)
js/app.js
import $ from "jquery" $(function(){ const editorSubmit = $('#editor__submit') editorSubmit.on("click",function(e){ e.preventDefault(); // 送られるデータはCKEDITOR.instances.name属性名.getData()で取得可能 const data = CKEDITOR.instances.editor.getData(); // プレビュー画面に表示 $(".preview").html(data) }) })テキトーにCSSも
.container { margin: 50px; width: 60%; } #editor__submit { margin: 30px auto; padding: 10px; border-radius: 2px; border: 1px solid #d3d3d3; background: #d3d3d3; width: 100%; } .preview { min-height: 500px; border: 1px solid #d3d3d3; padding: 20px; line-height: 3; }結果
意外と簡単に実装できてびっくりでした!
余計なプラグイン入れてるのでごちゃごちゃしてますが、取捨選択することでイイカンジのエディタが簡単に作れそうです
何か気づきや不備あればぜひコメントください
- 投稿日:2019-10-02T12:25:00+09:00
ローディング画面を作ったら自信作になった件
今日、teratailの回答でローディング画面を作ったらなかなかうまくできたので記事にします。
以下コードとCodePenです。<body> <div id="loading"> LOADING </div> <main> <div class=menu> MeNu </div> <img id=topimage src=https://4k-wallpaper.com/3840x2160/4k-Ultra-HD_00348.jpg> </main> </body>* { margin: 0; padding: 0 } main { display:none } #topimage { width: 100% } .menu { height:2em; line-height: 2em; position: absolute; top: -2em }window.onload = function () { $("#loading").delay(1000).fadeOut(); $("main").delay(2000).fadeIn(); $("#topimage").delay(3000).animate({width:"90%",marginLeft:"5%",marginTop:"2em",height:"auto"},"slow", "swing"); $(".menu").delay(3000).animate({top:"0"},"slow", "swing"); }See the Pen PoYMbNp by asuchi0819 (@asuchi0819) on CodePen.
頑張った〜
- 投稿日:2019-10-02T11:48:47+09:00
React-Reduxが難しい? それは過去の話だ! ~ ToDoアプリを最小限の労力で記述する ~
Reduxが難しい? それは過去の話だ! ~ ToDoアプリを最小限の労力で記述する ~
React-Reduxを簡単に利用するためのパッケージ@jswf/redux-moduleを利用して、最小限の労力でToDoアプリを作成するという内容です。
プログラムはファイル一つだけにまとめました。
内容は以下のように構成されています。
- データ構造の定義
- データ操作用クラスの作成
- データ入力用コンポーネント
- データ表示用コンポーネント
今回はReduxラッパーの機能を使うため、データの入力と表示はコンポーネントは分けてあります。
ここで見ていただきたいのは、propsもuseStateも使っていないということです。
全てのデータはReduxのStore上に格納されます。
しかしReduxを使う上での前提となるFlux的な定義は一切書く必要がありません。
ReduxModuleクラスを継承することによって、読み書き全ての手続きがsetStateとgetStateメソッドに集約されます。作ったもの
ソースコード
index.tsximport React from "react"; import * as ReactDOM from "react-dom"; import { createStore } from "redux"; import { Provider } from "react-redux"; import { ModuleReducer, useModule, ReduxModule } from "@jswf/redux-module"; /** *データ構造の定義(TypeScript使用時) * * @export * @interface TodoState */ interface TodoState { //入力中データの保持 input: { title: string; desc: string; }; //TODOリスト todos: { id: number; title: string; desc: string; done: boolean; }[]; //TODOのID附番表index index: number; } /** *Todoデータ管理用クラス * * @export * @class TodoModule * @extends {ReduxModule<TestState>} */ export class TodoModule extends ReduxModule<TodoState> { //初期値 protected static defaultState: TodoState = { todos: [], input: { title: "", desc: "" }, index: 0 }; /** *ToDoリストを返す * * @returns * @memberof TodoModule */ public getTodos() { return this.getState("todos")!; } /** *ToDoを追加(追加データはStoreに入っているので引数不要) * * @memberof TodoModule */ public addTodo() { //indexのインクリメント const index = this.getState("index")! + 1; this.setState({ index }); //必要データの読み出し const title = this.getState("input", "title")!; const desc = this.getState("input", "desc")!; //todoを追加 const todos = [ ...this.getState("todos")!, { id: index, title, desc, done: false } ]; this.setState({ todos })!; } /** *ToDoの状態管理 * * @param {number} id * @param {boolean} done * @memberof TodoModule */ public updateDone(id: number, done: boolean) { const srcTodos = this.getState("todos")!; //状態の書き換え const todos = srcTodos.map(todo => todo.id === id ? { ...todo, done } : todo ); this.setState({ todos }); } /** *ToDoの削除 * * @param {number} id * @memberof TodoModule */ public remove(id: number) { const srcTodos = this.getState("todos")!; //削除データを除外 const todos = srcTodos.filter(todo => todo.id !== id); this.setState({ todos }); } } /** *入力フォームコンポーネント * * @returns */ function FormComponent() { const todoModule = useModule(TodoModule); return ( <div style={{ textAlign: "center" }}> <div> <div>タイトル</div> <input style={{ width: "20em" }} value={todoModule.getState("input", "title")!} onChange={e => todoModule.setState(e.target.value, "input", "title")} /> <div>説明</div> <textarea style={{ width: "20em", height: "5em" }} value={todoModule.getState("input", "desc")!} onChange={e => todoModule.setState(e.target.value, "input", "desc")} /> <div> <button onClick={() => todoModule.addTodo()}>Todoを作成</button> </div> </div> </div> ); } /** *ToDo出力コンポーネント * * @returns */ function TodoListComponent() { const todoModule = useModule(TodoModule); const todos = todoModule.getState("todos")!; return ( <div style={{ display:"flex",flexDirection: "column" ,alignItems:"center"}}> {todos.map(todo => ( <div key={todo.id} style={{ display: "inline-block", width: "20em", marginTop: "1em", border: "solid 1px", textAlign:"center" }} > <div> {todo.id}:{todo.title}{" "} <span style={{ cursor: "pointer" }} onClick={() => todoModule.updateDone(todo.id, !todo.done)} > {todo.done ? "完了" : "未完了"} </span> {todo.done && ( <span style={{ cursor: "pointer" }} onClick={() => todoModule.remove(todo.id)} > 削除 </span> )} </div> <div>{todo.desc}</div> </div> ))} </div> ); } //Reduxに専用のReducerを関連付ける //他のReducerと併用することも可能 const store = createStore(ModuleReducer); ReactDOM.render( <Provider store={store}> <FormComponent /> <TodoListComponent /> </Provider>, document.getElementById("root") as HTMLElement );まとめ
ほぼReduxの影や形が消え去っています。
今回の内容はコンポーネント間の連係や、データをStoreに集約することが目的であれば、かなり便利に使えると思います。パフォーマンスを考えて作る場合は、副作用の影響範囲を最小限に抑えるためデータ操作用のクラスを細分化したり、書き込みのみしか利用しないコンポーネント上ではwriteOnly属性を付けたりとチューニングが必要になりますが、それは別の記事で解説を入れたいと思います。
Reduxの定義に疲れ果てた方はぜひ使ってみてください。
リンク
- 投稿日:2019-10-02T11:42:02+09:00
[メモ]FormDataオブジェクトにあるデータを全て削除する方法
概要
FormDataにあるすべてのデータ(エントリー)を削除する
ポイント
formData.keys()やformData.entries()のイテレーター内で削除せず、イテレーターの外側で削除する。
FormDataオブジェクトのデータを全て削除するfunction deleteAllFormData(formData) { const keys = []; for (const key of formData.keys()) { keys.push(key); } for (const idx in keys) { formData.delete(keys[idx]); } }サンプルコード(html)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Delete all formdata</title> </head> <body> <form action="#" method="post" id="myForm" enctype="multipart/form-data"> <input type="hidden" name="myKey01" value="myValue01"> </form> <script> function deleteAllFormData(formData) { const keys = []; for (const key of formData.keys()) { keys.push(key); } for (const idx in keys) { formData.delete(keys[idx]); } } const formData = new FormData(document.querySelector("#myForm")); formData.append("myKey02", "myValue02"); formData.append("myKey03", "myValue03"); console.log("Show form entries"); for (const [key, value] of formData.entries()) { console.log(`key:${key} value:${value}`); } console.log("Delete!"); deleteAllFormData(formData); console.log("Show form again"); for (const [key, value] of formData.entries()) { console.log(`key:${key} value:${value}`); } </script> </body> </html>実行結果(ログ)Show form entries key:myKey01 value:myValue01 key:myKey02 value:myValue02 key:myKey03 value:myValue03 Delete! Show form againアンチパターン
反復中に自身を変更する以下のようなコードはアンチパターン
for (const key of formData.keys()) { formData.delete(key); }(Javaでも、同期リストのIteratorで反復中に自身を変更(削除も含む)するとConcurrentModificationExceptionが発生するパターン)
- 投稿日:2019-10-02T09:46:46+09:00
canvasで描いた絵のURLをDBに保存する
概要
私は9月ごろから独学でRuby on Railsを始めたのですが、題名の通りcanvasで描いた絵をURLに変換してDBに保存する際に数日詰まったので自分への戒めのために今回の記事を書く運びとなりました。
詰まったところ
ArgumentError (When assigning attributes, you must pass a hash as an argument.):
ハッシュにしてから受け渡してください的なエラーが出る。
受け渡したデータ
JavaScriptでcanvasのデータをURL化しているため、そのままJavaScriptでPOSTしました。
こんなかんじ//変数imageにはcanvasのURLが入っています function send_url(image){ var form = document.createElement('form'); var request = document.createElement('input'); form.method = 'POST'; form.action = 'save'; request.type = 'hidden'; request.name = 'text'; request.value = image; form.appendChild(request); document.body.appendChild(form); form.submit(); }受け取り側はこのようになっています
private def post_params params.require(:text) end endこの形式で受け取るとpost_paramsは送信されてきた文字列だけをデータに持ちます。
修正後
先ほどの関数post_paramsを次のように書き換えました
def post_params img = params.require(:text) hash_url = {"image_url" => img} return hash_url end力技です。
最後に
セキュリティなどの観点で見てこれが大丈夫なやり方なのかはわかりませんが、とりあえずこのハッシュにしたデータを渡すことで無事URLをDBに渡すことができました。
- 投稿日:2019-10-02T09:18:10+09:00
【Google Apps Script】チェックボックスでスプレッドシートの特定行の表示/非表示を切り替える
はじめに
スプレッドシートにチェックボックスを配置し、そのアクティブ状態によって指定行を表示/非表示の切り替えをしたいと要望がありました。
GASを使えばいけそうなので、早速試してみたので書いてみます。完成図
下準備
スプレッドシートを新規作成。
1. A1〜A3に「A」「B」「C」と入力。
2. B1〜B3にチェックボックスを配置。チェックボックスは挿入→チェックボックスで配置できます。
3. C1〜C3に、カンマ区切りの適当な数字を入れます。ここに入れた行数が、チェックした場合の表示する行になります。ツール→スクリプトエディタを選択し、以下コードを貼り付けます。
コード全文
var CHECKBOX_COLUMN = 2; // チェックボックスの列 var CHECKBOX_COUNT = 3; // チェックボックスの数 var HIDDEN_FIELD_START = 6; // 何行目から消すのか var HIDDEN_FIELD_END = 30; // 何行目まで消すのか // スプレッドシートの値が変更された時に発火 function changeEvent() { var sheet = SpreadsheetApp.getActiveSheet(); var activeCell = sheet.getActiveCell(); if (activeCell.getColumn() == CHECKBOX_COLUMN){ // チェックボックス列の値が変更された場合 checkBoxEvent(sheet); } } // チェックボックスのチェック状態に合わせて表示・非表示を切り替える function checkBoxEvent(sheet) { var rows = sheet.getRange(1, CHECKBOX_COLUMN, CHECKBOX_COUNT, 1).getValues(); hiddenFields(sheet); for (var i = 0; i < rows.length; i++) { if (rows[i][0] === true) { var showRows = sheet.getRange(i + 1, CHECKBOX_COLUMN + 1).getValue().split(','); for (var j = 0; j < showRows.length; j++) { sheet.showRows(showRows[j]); // チェックボックスの1個右のセルの価に記載されている行数を表示する } } } } // 非表示行エリアを全非表示にする function hiddenFields(sheet) { sheet.hideRows(HIDDEN_FIELD_START, HIDDEN_FIELD_END - HIDDEN_FIELD_START + 1); }動作確認
スプレッドシートのカーソルをB列のどこかに置きます。
if (activeCell.getColumn() == CHECKBOX_COLUMN){ // チェックボックス列の値が変更された場合ソースコードで見るとこの部分、B列の値変更イベントにのみ発火するように設定しているためです。
スクリプトエディタで実行→関数の実行→changeEventを選択。
実行許可のウィンドウが出るので、過去記事の過去記事のスクョを参考にしてください。スプレッドシートの10〜30行目が非表示になり、チェックONになっているセル右隣で指定している行のみ表示されれば成功です。
トリガーの設定
チェックボックス変更した場合、自動的に行の表示/非表示されるように、スプレッドシートの変更イベントをトリガーとしてchangeEvent関数が自動実行されるよう設定します。
スクリプトエディタ「編集→現在のプロジェクトのトリガー」もしくは時計アイコンからトリガー設定画面に飛びます。
右下の「トリガーを追加」から、下記の通り設定します。
- 投稿日:2019-10-02T03:54:41+09:00
【Blob・File・Base64・データURL・FileReader】 それぞれの特徴とブラウザへの表示について
はじめに
この記事では、ファイルを受け取ってからブラウザ上に表示するまでに登場する、インターフェースやオブジェクトの特徴を調べ、みなさんが要件に適したファイル操作を選択できるよう祈りながら書きました。
それぞれの特徴
Blob
- HTML5のFile APIで定義されたインターフェースである
- バイナリ・ラージ・オブジェクトの略である
- Blobオブジェクトには、生のバイナリデータが格納されている
- プロパティはsizeとtypeのみで、中のデータに直接アクセスすることはできない
- Blobオブジェクトはimmutable(不変)であり、を直接編集することはできない
- が、sliceメソッドによる分割や、コンストラクタによる結合を行うことができる
https://developer.mozilla.org/ja/docs/Web/API/Blob
https://ja.javascript.info/blobFile
- HTML5のFile APIで定義されたインターフェースである
- Blobインターフェースを継承しているため、Blobを引数に取るメソッドに渡すことができる
- 異なるのは、よりファイルを扱いやすくするために、name,lastModifiedプロパティが追加されている点
<input type="file">
にローカルファイルを与えると、files属性か、onchangeイベント経由で、 FileListオブジェクトが得られる。FileListオブジェクトに対してインデックスを渡すことでFileオブジェクトを取得することができるhttps://developer.mozilla.org/ja/docs/Web/API/File
https://ja.javascript.info/fileFileReader
- FileReaderは、Blob(File)オブジェクト内のバイナリデータを読み込むためのオブジェクト
- FileReaderオブジェクトには、下記のようなメソッドがあり(非推奨のものは除く)、Blobオブジェクトを様々な形式で読み取ることができる
readAsArrayBuffer()
→ArrayBufferreadAsDataURL()
→DataURLreadAsText()
→ArrayBufferabort()
→読み取り中断https://developer.mozilla.org/ja/docs/Web/API/FileReader
https://ja.javascript.info/fileBase64
- Base64というクラスやインターフェースは存在せず、バイナリデータをStringに変換するためのエンコード方式である
- エンコード前のバイナリデータと比べて約33~37%データサイズが増加する
データURL
data:[<mediatype>][;base64],<data>
という形式で記述されたURLである- 例えばテキストデータでは、
data:text/plain;base64,dGVzdA==
のような感じのURLで、これをアドレスバーに入力するとtestと表示される- jpeg画像ファイルを示すデータURLは
data:image/jpeg;base64,
という文言から始まる- URL内にテキストや画像データが埋め込まれているため、URL自体が比較的大きいサイズになる
BlobやFileに格納されているファイルをブラウザ上に表示する方法
以下、画像ファイルの表示を例にして説明する。
画像ファイルをブラウザ上に表示するためには、imgタグのsrc属性にURL(データURLを含む)を与える必要がある。URLを取得するには2つの方法がある
- Blob(File)をデータURLとして読み取る
- Blobを参照するURLを生成する以下、方法とメリット・デメリットを記述する
BlobをデータURLとして読み取る
const reader = new FileReader(); reader.onload = function(e) { console.log(e.target.result) //データURLがコンソールに表示される } reader.readAsDataURL(blob) //ここでBlobオブジェクトを読み込み、読み込みが完了したタイミングでreader.onloadに定義した関数が実行されるメリット
- ファイルの読み込みを非同期で行うことができる。
- URLそのものにデータが埋め込まれているため、URLを直接サーバーに渡すことが可能。
デメリット
- Base64エンコードの仕様に則り、データサイズが133%ほどに増える
- メモリ上にファイルを展開するため、メモリを圧迫する
Blobを参照するURLを生成する
const imageUrl = window.URL.createObjectURL(blob) //URL生成 console.log(imageUrl) //データURLがコンソールに表示される window.URL.revokeObjectURL(imageUrl) //URL破棄メリット
- URLの生成が即時完了する
デメリットあるため、
- 一時的なURLであり、ブラウザを開き直したり、他端末や他ブラウザに渡してもアクセスできない
window.URL.revokeObjectURL()
によってURLを破棄しないと、ブラウザを閉じるまでメモリ上にURLが残ってしまう
- 投稿日:2019-10-02T00:34:32+09:00
Vue.jsでスクロール時のイベントを定義する
やりたいこと
わかりづらいので、具体的にやりたかったことを書きます。
自分が作っていたアプリでは、「ページを下のほうにスクロールしたら
トップへ戻る
ボタンを出したい」といった、「スクロールしたときに何かする」的な処理が結構ありました。そのたびに、↓こんな感じに
created
とdestroyed
でイベントを登録・解除するのがめんどいな、と。<script> export default { created () { window.addEventListener('scroll', this.onScroll) }, destroyed () { window.removeEventListener('scroll', this.onScroll) }, methods: { onScroll () { // Do something } } } </script>そこで、↓こんな感じに書くだけで、スクロールしたときのフックを定義できるようにしました。
<script> export default { onScroll () { // Do something } } </script>実装
公式ドキュメントをぱっと見した限り、ディレクティブをカスタマイズする手段はあるようですが、上記のようなフックのカスタマイズは見つかりませんでした。
なので、GlobalなMixinを作って実現しました。
main.jsimport Vue from 'vue' // .. import GlobalMixin from './GlobalMixin' Vue.mixin(GlobalMixin) // ..↓onScrollが定義されていた場合、windowのscrollイベントに登録し、
destroyed
で削除します。GlobalMixin.vue<script> export default { mounted () { // Register onScroll event if (this.$options.onScroll) { window.addEventListener('scroll', this.$options.onScroll) } }, destroyed () { // Unregister onScroll event if (this.$options.onScroll) { window.removeEventListener('scroll', this.$options.onScroll) } } } </script>これだけです。
(余計なお世話かもしれませんが、↑このmixinはむやみに拡張しないでください)
そして、冒頭のように各コンポーネントで
onScroll
という名前でイベントを定義するだけです。<script> export default { data () { return { showButton: false } }, onScroll () { this.showButton = window.scrollY > 100 } } </script>余談
公式のカスタムディレクティブの例でまさにスクロール時のイベントを定義するサンプルがあったんですが、ちょっとしっくりこないです…。
<div id="app"> <h1 class="centered">Scroll me</h1> <div v-scroll="handleScroll" class="box" > <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. A atque amet harum aut ab veritatis earum porro praesentium ut corporis. Quasi provident dolorem officia iure fugiat, eius mollitia sequi quisquam.</p> </div> </div>
@scroll
的な感じで、.box
がスクローラブルな要素で、そのonscrollに定義される感じがするけど、結局window
全体のonscrollに定義してるし、そもそも外からイベントを定義したいケースも思いつかない。
handleScroll
の引数に渡したelementを直接弄ってるのも手続き的で気持ちよくないです…。
- 投稿日:2019-10-02T00:34:32+09:00
Vue.jsでスクロール時のイベントを便利に定義する
やりたいこと
わかりづらいので、具体的にやりたかったことを書きます。
自分が作っていたアプリでは、「ページを下のほうにスクロールしたら
トップへ戻る
ボタンを出したい」といった、「スクロールしたときに何かする」的な処理が結構ありました。そのたびに、↓こんな感じに
created
とdestroyed
でイベントを登録・解除するのがめんどいな、と。<script> export default { created () { window.addEventListener('scroll', this.onScroll) }, destroyed () { window.removeEventListener('scroll', this.onScroll) }, methods: { onScroll () { // Do something } } } </script>そこで、↓こんな感じに書くだけで、スクロールしたときのフックを定義できるようにしました。
<script> export default { onScroll () { // Do something } } </script>実装
公式ドキュメントをぱっと見した限り、ディレクティブをカスタマイズする手段はあるようですが、上記のようなフックのカスタマイズは見つかりませんでした。
なので、GlobalなMixinを作って実現しました。
main.jsimport Vue from 'vue' // .. import GlobalMixin from './GlobalMixin' Vue.mixin(GlobalMixin) // ..↓onScrollが定義されていた場合、windowのscrollイベントに登録し、
destroyed
で削除します。GlobalMixin.vue<script> export default { mounted () { // Register onScroll event if (this.$options.onScroll) { window.addEventListener('scroll', this.$options.onScroll) } }, destroyed () { // Unregister onScroll event if (this.$options.onScroll) { window.removeEventListener('scroll', this.$options.onScroll) } } } </script>これだけです。
(余計なお世話かもしれませんが、↑このmixinはむやみに拡張しないでください)
そして、冒頭のように各コンポーネントで
onScroll
という名前でイベントを定義するだけです。<script> export default { data () { return { showButton: false } }, onScroll () { this.showButton = window.scrollY > 100 } } </script>余談
公式のカスタムディレクティブの例でまさにスクロール時のイベントを定義するサンプルがあったんですが、ちょっとしっくりこないです…。
<div id="app"> <h1 class="centered">Scroll me</h1> <div v-scroll="handleScroll" class="box" > <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. A atque amet harum aut ab veritatis earum porro praesentium ut corporis. Quasi provident dolorem officia iure fugiat, eius mollitia sequi quisquam.</p> </div> </div>
@scroll
的な感じで、.box
がスクローラブルな要素で、そのonscrollに定義される感じがするけど、結局window
全体のonscrollに定義してるし、そもそも外からイベントを定義したいケースも思いつかない。
handleScroll
の引数に渡したelementを直接弄ってるのも手続き的で気持ちよくないです…。