20210410のJavaScriptに関する記事は18件です。

無名関数と即時関数

無名関数 引数になる関数が1回きりしか使わない場合に使う(高階関数、即時関数) コードがシンプルになる 関数名の重複を防ぐ 関数名を考えずに済む 文頭にfunctionを書かなければ無名関数として定義できる 関数名がなくても困らない状況において使える const sum = (x,y) => { return x + y; } sum(3,5); 即時関数 即時実行される無名関数 関数を定義すると同時に実行するための構文 即時関数で使用された変数は「ローカル変数」となり即時関数外から参照することはできない グローバルスコープ名の汚染回避 構文 (() => { //いろいろ処理 })(); const sum = ((a,b) => { const result = a + b; return result; })(1,2); console.log(sum);//3
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

API を 1 回だけ呼んであとは同じ結果を得たい場合、値ではなく Promise をキャッシュする

REST API の呼び出し結果を、集中的に何度も使いたいような場合、API のレスポンスをキャッシュするのではなく、Promise をキャッシュすることで、API が 1 回だけ呼ばれることを保証できます。 何もキャッシュしない場合 まず最初に、何もキャッシュしない場合を見てみます。 import axios from 'axios' class App { /** * API に GET リクエストを送信するメソッド。何もキャッシュしないバージョンです。 */ async request(url) { return await axios.get(url) } /** * API を使ってなにかするメソッドです。 */ async doSomethingWithAPI(url, index) { const result = await this.request(url) console.log(index, result.data) } async main() { const url = 'http://localhost:8000/api/test.json' // API の結果を使いたい処理が 10 個あるものとします。 for (let i = 0; i < 10; i++) { // 挙動をわかりやすくするため少しタイミングをずらします。 await this.sleep(1) // 並列で実行したいので await しません。 this.doSomethingWithAPI(url, i) } } /** * 非同期の sleep 実装。記事の本題とは関係ありません。 */ async sleep(msec) { return new Promise(resolve => setTimeout(resolve, msec)) } } const app = new App() app.main() API は、リクエストするたびにカウントを1ずつ増やした結果を返すものとします。(サーバのコードは後述します) 実行結果は以下のようになります。何もキャッシュしていないので、10 回 API が呼ばれて、カウントが 10 まで増えます。 0 { count: 1 } 1 { count: 2 } 2 { count: 3 } 3 { count: 4 } 4 { count: 5 } 5 { count: 6 } 6 { count: 7 } 7 { count: 8 } 8 { count: 9 } 9 { count: 10 } 値をキャッシュすると何が起きるか request(url) メソッドを以下で置き換えてみます。API レスポンスの値をキャッシュするバージョンです。 constructor() { this.cache = {} } /** * 渡された URL をキーにして API レスポンスをキャッシュします。 */ async request(url) { if (!this.cache[url]) { this.cache[url] = await axios.get(url) } return this.cache[url] } これを実行した結果は、以下のようになります。 0 { count: 1 } 7 { count: 1 } // キャッシュにヒット 1 { count: 2 } 2 { count: 3 } 3 { count: 4 } 4 { count: 5 } 8 { count: 5 } // キャッシュにヒット 5 { count: 6 } 9 { count: 6 } // キャッシュにヒット 6 { count: 7 } await axios.get(url) でレスポンスを待っている間に、どんどん次のメソッド呼び出しに処理が移ってしまうため、全部で 7 回 API リクエストが発生しています。 左端の番号 7 の呼び出しが、番号 0 のキャッシュにヒットしています。7 は await する必要がないので、レスポンスを待っている 1 〜 6 より先に処理が終わります。 このように、値をキャッシュすると、不要な API 呼び出しが発生してしまうことがあります。 API 結果が短期間に変化しないのであれば、 7 回も API を呼ぶ意味がありません。同じ結果を毎回返せばいいはずです。 Promise をキャッシュする 不要な API 呼び出しを避けるには、API の値ではなく Promise をキャッシュします。 /** * 渡された URL をキーにして API 呼び出しの Promise をキャッシュします。 */ async request(url) { if (!this.cache[url]) { this.cache[url] = axios.get(url) } return this.cache[url] } もう一度、値をキャッシュするバージョンを見てみましょう。 /** * 渡された URL をキーにして API レスポンスをキャッシュします。 */ async request(url) { if (!this.cache[url]) { this.cache[url] = await axios.get(url) } return this.cache[url] } ほとんど一緒です!違いは、 await axios.get(url) の await を削った一箇所だけです。 たったこれだけの修正で、API が 1 回だけ呼ばれることを保証できます。request() メソッドの呼び出し側は何も変えていません。 0 { count: 1 } 1 { count: 1 } 2 { count: 1 } 3 { count: 1 } 4 { count: 1 } 5 { count: 1 } 6 { count: 1 } 7 { count: 1 } 8 { count: 1 } 9 { count: 1 } Promise が、API のレスポンスが得られるまで待ってから、一斉に値を返す役目を果たします。また、API レスポンスが返った後も、同じ Promise を使い続けることができます。 現実世界の例 GraphQL で、各ノードの子が一斉にリゾルバを実行しようとするのに、REST API の呼び出しが 1 回しか発生しないのはどういう仕組みになっているんだろう、というのを調べていて知りました。 if (request.method === 'GET') { let promise = this.memoizedResults.get(cacheKey); if (promise) return promise; promise = performRequest(); this.memoizedResults.set(cacheKey, promise); return promise; } else { this.memoizedResults.delete(cacheKey); return performRequest(); } なるほど、こうすればいいのか!と、ちょっと感動しました。 これほど大事そうな実装パターンであれば名前が付いていても良さそうなものですが、決まった名前が見つけられていません。Memoized Async Function という感じでしょうか。 テスト用の API サーバ 以下は、上のテストコードで使った、カウントをインクリメントする API サーバです。 import http from 'http' const port = 8000 let count = 0 http.createServer(async (req, res) => { // 少しタイミングを遅らせるため、console.log() を出力します。 console.log(new Date(), req.url) count += 1 res.writeHead(200, {'Content-Type': 'application/json'}) res.write(`{"count": ${count}}`) res.end() }).listen(port) console.log(`Server running at http://localhost:${port}`) package.json を含むサンプルを以下に置きました。使っている Node.js のバージョンは 14.16.1 です。 https://gist.github.com/koseki/c964ec15719a18e02d0f3576be6634de 参考にした記事 Promiseを一回だけ実行する(非同期メモ化) Memoize JavaScript Promises for Performance
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascript 備忘録3<変数について>

■変数の宣言について →Javascriptは「静的片付け言語」 →変数を宣言する時に、型まで宣言する必要がない ■andの応用的使い方 function hello(name) { // nameに値が入っている場合は、その値で、入っていない場合はAnyoneが返る name = name || 'Anyone'; console.log(name); } let name; // nameに値が入っていた場合に、hello関数を実行する if (name) { console.log(name); } // 上のと同じ処理だが、1行で済む name && hello(name);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CSSのradial-gradient、conic-gradientで描いた円グラフをJavaScriptで可変

今回もこちらの続きのような感じですが、単項目の進捗度ではなく複数アイテムの円グラフを描いてみました。 JavaScript circle_graph.js 'use strict'; function drawGraph(obj) { const baseElement = document.getElementById(obj.targetId); if(baseElement === null) return; const bgColor = obj.backgroundColor ?? 'transparent', size = obj.size ?? 200, weight = obj.weight ?? 0.5, sort = obj.sort ?? 1, items = (obj.items ?? []).slice(); // ソート除外アイテム退避 const fixed = []; for(let i = 0; i < items.length; i++) { if(items[i].fixed !== undefined && 'fr'.indexOf(items[i].fixed[0]) >= 0) { fixed.push(items.splice(i--, 1)[0]); } } // ソート if(sort !== 0) items.sort(function(a, b) { return (b.v - a.v) * sort;}); // ソート除外アイテムを戻し if(fixed.length) { fixed.forEach(i => { if(i.fixed === 'f') items.unshift(i); else if(i.fixed === 'r') items.push(i); }); } // トータル算出 let total = 0; items.forEach(i => { total += +i.v;}); // 百分率追加 items.forEach(i => { i.p = total > 0 ? 100 * i.v / total : 0;}); // conic-gradientへ渡すパラメータ生成 const degree = []; let t = 0; items.forEach(i => { if(i.p > 0) degree.push(`${i.c} ${t}deg ${t = t + i.p * 3.6}deg`); }); if(total === 0) degree.push('#ddd 0 360deg'); // グラフ描画 baseElement.innerHTML = "<div class='circle'><div class='graph'></div></div>"; const circle = baseElement.querySelector('.circle'), graph = circle.querySelector('.graph'), outerRadius = size / 2, innerRadius = outerRadius * (1 - weight); circle.style.position = 'relative'; graph.style.position = 'absolute'; graph.style.mixBlendMode = 'multiply'; baseElement.style.width = baseElement.style.height = circle.style.width = circle.style.height = graph.style.width = graph.style.height = `${size}px`; circle.style.background = `radial-gradient(${bgColor} ${innerRadius - 1}px, #fff ${innerRadius}px, #fff ${outerRadius - 1}px, ${bgColor} ${outerRadius}px)`; graph.style.background = `radial-gradient(#fff ${innerRadius - 1}px, #0000 ${innerRadius}px, #0000 ${outerRadius - 1}px, #fff ${outerRadius}px), conic-gradient(${degree.join(', ')})`; return { total: total, items: items, }; } サンプルHTML <!DOCTYPE html> <html lang='ja'> <head> <meta name='viewport' content='width=device-width,initial-scale=1'> <script src='./circle_graph.js'></script> </head> <body style='background-color: #ccc'> <div id='id1'></div> <script> 'use strict'; const obj = { // 描画先要素ID targetId: 'id1', // 背景色 backgroundColor: 'transparent', // グラフ直径 size: 250, // 太さ 0~1 weight: 0.5, // ソート 1:降順 -1:昇順 0:配置順 sort: 1, // アイテム配列 items: [ // c:色, v:数, n:名前 [,fixed:ソート除外('f'前方固定|'r'後方固定)] {c:'red', v:239, n:'name1'}, {c:'orange', v:110, n:'name2'}, {c:'blue', v:75, n:'name3'}, {c:'cyan', v:33, n:'name4'}, {c:'yellow', v:428, n:'name5'}, {c:'magenta', v:183, n:'name6'}, {c:'#fff', v:31, n:'other1', fixed:'f'}, {c:'#444', v:230, n:'other2', fixed:'r'}, ], }; window.addEventListener('DOMContentLoaded', function(){ const res = drawGraph(obj); console.log(res); }); </script> </body> </html> こんな感じになります。 動作デモ スライダーでの入力反映サンプル <!DOCTYPE html> <html lang='ja'> <head> <meta charset='utf-8'> <meta name='viewport' content='width=device-width,initial-scale=1'> <script src='./circle_graph.js'></script> <style> .panel { background-color: #fff8; position: fixed; bottom: 0px; left: 0px; padding: 8px; width: 100%; } .colorcode, .name { width: 80px; } .picker { width: 28px; height: 20px; padding: 0px; } .slider { width: 50%; } .view { display: flex; padding: 10px; } div[id=id1] { display: inline-table; } div[id=id2] { margin: 8px; } #icon { position: absolute; top: 0px; right: 20px; width: 20px; height: 20px; cursor: pointer; } </style> </head> <body style='background-color: #ccc'> <div class='view'> <div id='id1'></div> <div id='id2'></div> </div> <div class='panel'> <span id='icon'>▼</span> <input type='range' min='10' max='500' id='size' oninput='update()'>直径&emsp; <input type='range' min='0' max='1' step='0.02' id='weight' oninput='update()'>太さ&emsp; <select id='sort' onchange='update()'> <option value='0'>配置順</option> <option value='1'>降順</option> <option value='-1'>昇順</option> </select> <div class='items'> <br> <span class='p'> <input type='checkbox' class='check' checked onclick='chgActive()'> <input type='text' class='colorcode' value='#000000' oninput='update()'> <input type='color' class='picker' oninput='update(1)'> <input type='text' class='name' value='name1' oninput='update()'> <input type='range' class='slider' min='0' max='1000' value='0' oninput='update()'> <select class='fixed' onchange='update()'> <option value=''>---</option> <option value='f'>前方固定</option> <option value='r'>後方固定</option> </select> </span> </div> </div> <script> 'use strict'; const obj = { targetId: 'id1', size: 250, weight: 1, sort: -1, items: [ {c:'#ff0000', v:50, n:'name1'}, {c:'#00ff00', v:100, n:'name2'}, {c:'#ffff00', v:150, n:'name3'}, {c:'#0000ff', v:200, n:'name4'}, {c:'#ff00ff', v:250, n:'name5'}, {c:'#00ffff', v:300, n:'name6'}, {c:'#ffffff', v:150, n:'other1'}, {c:'#000000', v:150, n:'other2'}, ], }; window.addEventListener('DOMContentLoaded', function() { const panelItems = document.querySelector('.panel .items'); let s1 = panelItems.innerHTML, s2 = s1.repeat(obj.items.length); panelItems.innerHTML = s2; const colorcodes = document.querySelectorAll('input[class=colorcode]'), sliders = document.querySelectorAll('input[class=slider]'), pickers = document.querySelectorAll('input[class=picker]'), names = document.querySelectorAll('input[class=name]'), fixeds = document.querySelectorAll('select[class=fixed]'); for(let i = 0; i < obj.items.length; i++) { pickers[i].value = colorcodes[i].value = obj.items[i].c; sliders[i].value = obj.items[i].v; names[i].value = obj.items[i].n; if(obj.items[i].fixed !== undefined && 'fr'.indexOf(obj.items[i].fixed) >= 0) { fixeds[i].value = obj.items[i].fixed; } } document.getElementById('size').value = obj.size; document.getElementById('weight').value = obj.weight; document.getElementById('sort').value = obj.sort; putList(drawGraph(obj)); document.getElementById('icon').addEventListener('click', function(){ const p = document.querySelector('.panel'); if(p.style.height !== '22px') { this.textContent = '▲'; p.style.height = '22px'; } else { this.textContent = '▼'; p.style.height = ''; } }); }); function update(s) { const checkboxs = document.querySelectorAll('input[type=checkbox]'), colorcodes = document.querySelectorAll('input[class=colorcode]'), sliders = document.querySelectorAll('input[class=slider]'), pickers = document.querySelectorAll('input[class=picker]'), names = document.querySelectorAll('input[class=name]'), fixeds = document.querySelectorAll('select[class=fixed]'); const p = []; obj.items = []; for(let i = 0; i < sliders.length; i++) { if(!checkboxs[i].checked) continue; if(s) colorcodes[i].value = pickers[i].value; else pickers[i].value = cvtColorCode(colorcodes[i].value); const colorcode = colorcodes[i].value, slider = sliders[i].value, name = names[i].value, fixed = fixeds[i].value; p.push({c:colorcode,v:slider,n:name,fixed:fixed}); obj.items.push({c:colorcode,v:+slider,n:name,fixed:fixed}); } obj.size = document.getElementById('size').value; obj.weight = document.getElementById('weight').value; obj.sort = document.getElementById('sort').value; putList(drawGraph(obj)); } function putList(obj) { let str = ''; obj.items.forEach(i => { if(i.v > 0) { str += `<span style='color:${i.c}'>■</span> ${i.n.replace(/^\s*$/, '----').replace(/</g, '&lt;')} : ${i.v} (${i.p.toFixed(1)}%)<br>`; } }); str += `<br>&emsp;TOTAL : ${obj.total}`; document.getElementById('id2').innerHTML = str; } function chgActive() { const ps = document.querySelectorAll('span.p'), checkboxs = document.querySelectorAll('input[type="checkbox"]'); for(let i = 0; i < checkboxs.length; i++) { ps[i].style.opacity = checkboxs[i].checked ? '1' : '0.5'; } update(); } function cvtColorCode(s) { const dummy = document.createElement('p'); dummy.style.background = s; const c = dummy.style.background.split(','); for(let i = 0; i < c.length; i++) { c[i] = parseFloat(c[i].replace(/^\D+/,'')); if(!isNaN(c[i])) c[i] = c[i].toString(16).padStart(2, '0'); } return /^[\da-f]{2}$/i.test(c[0]) ? '#' + c.slice(0, 3).join('') : '#dddddd'; } </script> </body> </html> 動作デモ スライダーが多いのでスマホでは被って見辛いかもしれません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

関数

関数とは 値を受け取って値を返すもの ある一連の手続き(タスクや値計算)を再度利用できるようにまとめたもの メリット コード量の削減 再利用可能 コードの見通しが良くなる オブジェクトのメソッド=関数 構文 function 関数名(仮引数1,仮引数2) { //いろいろ処理 return 関数の返り値; } 文字数制限 function isTweetble(text) { return text.length <= 140; } console.log(isTweetble("foo")); //true returnを使わない方法 function alertTweetble(text) { if (text.length <= 140) { alert("you can tweet!"); } } alertTweetble("hoge");//you can tweet! 関数式 変数(定数)に関数を値として代入し、後からその変数を呼び出すことで関数を間接的に利用する方法 const isTweetble = function (text) { return text.length <= 140; } console.log(isTweetble("hoge"));//true 関数名は無くても動かすことができる(匿名関数) コールバック関数 引数に関数を入れたもの 引数に関数を受け取った関数を高階関数という それぞれの関数がコンパクトになり、役割が明確になる function unfollow() { console.log("フォローを外しました"); } function cancelTweet() { console.log("ツイートをキャンセルしました"); } //確認を完了させる処理(コールバック関数) function confirmed(fn) { if (window.confirm("実行しますか?")); fn(); } confirmed(cancelTweet); アロー関数 const tax = (number) => { return number * 1.08; } tax(300);//324 変数に関数を代入する形にして、function(){...}を() => {...}に変換 const isTweetble = (text) => { return text.length <= 140; } console.log(isTweetble("hoge"));//true
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのletは本当に悪なのか

はじめに 「constこそが唯一神である」というふざけた記事を書いてから時間が経ち、自分の意見も少し変わってきました。 この記事では「本当にプロダクションコードからletを排除し、constに置き換える必要があるのか」という根本的な部分をもう一度考えていきます。 Q. そもそもletは可読性が低いのか? 条件付きでNoといえます。以下のコードを御覧ください util.ts /** * 与えられた数値配列の偶数のみを合計した値を返す。 * @example sumIfEven([1, 2, 3, 4]) // -> 6 */ export function sumIfEven(numbers: number[]) { let ret = 0 for (const num of numbers) { if (num % 2 === 0) { ret += num } } return ret } このコードにはletを使っていますし、何ならretという命名を放棄したような変数名が使われていますが、別段読みづらくはないはずです。理由を言語化します。 JSDocのコメントが丁寧に書かれているため、読み手が想像しやすい。 スコープが短く、letの影響範囲が小さいため、読み手の負担が少ない。 したがって、ユーティリティ関数のようなスコープの短い場所で、コメントを付きでletを使うのは全く問題ないと考えられます。 Q. それでは、スコープが長くならないように適度に関数に分割すれば、letを使いまくっても問題ないのか? チームやプロジェクト、メンバーの得意、不得意によると考えます。 開発が進んでいくにつれて、スコープが長い関数というのが生まれそうになります。例えば、ユーザー登録フォームで登録ボタンが押された時の処理を書いた関数があるとします。開発当初は、あまり行数がないため、letを使ったとしましょう。 ユーザー登録時の処理 function onClickRegister() { let foo = 0 // ************* // 10行のコード // ************* } この時点では可読性がそこまで悪くないかもしれません。しかし、後に仕様が追加され、ユーザー登録時の処理が増えたとしましょう。 ユーザー登録時の処理 function onClickRegister() { let foo = 0 // ************* // 10行のコード // ************* // ************* // さらに10行のコード // ************* } スコープが長くなって、変数fooの影響範囲が大きくなってきました。読み手に強いる「fooの値を脳内に記憶しておく」という負担が大きくなってきます。さらに処理が追加されていくと、次第に「読みづらく、保守しづらいコード」になっていってしまいます。 別の関数に切り出せばもちろん。解決します。 別の関数に切り出して、それを呼び出す function onClickRegister() { doFoo() doBar() } しかし、「切り出す」と一口に言っても考えなければならない、難しいポイントがいくつもあります。 どのような粒度で切り出すか 切り出した関数の命名はどうするか 切り出した関数はどこに置くか 「これくらいエンジニアならできて当たり前」という意見が聞こえてきました。理想としてはそうなのですが、メンバーのスキルや、プロジェクトの納期によって、現実には難しい場面がでてくると考えています。 変に切り出したせいでむしろ読みづらくなる 納期が迫っているので、関数に切り出している余裕がない ということが発生する開発現場も想像できます。 そのため、メンバーの得意、不得意によって以下のような方針をとるのが良いと考えています。 mapやreduceが使える人は、積極的に使い、letを少なくしておく。 mapやreduceが苦手な人は、letやfor文を使っても良い。ただし、なるべく関数に切り出す Q. でも、mapとかreduce使うよりも、letとforループを使ったほうがパフォーマンスが良いのでは? はい。しかし、データ数が少ない場合は、パフォーマンスの差は小さいです。 ユーザーが気づかないくらいのパフォーマンス向上よりも、開発者が読みやすいようにコードを書いておき、バグが発生する可能性を減らしたほうが良いと考えることもできます。そのほうが、結局のところユーザーの利益につながるというわけです。 結論 短いスコープだったら、letを使っても何ら問題ない しかし、短いスコープを維持するのが難しい状況であれば、letの使用を控えることも選択肢のひとつになる letを使わないとコードが書けない人は、関数に切り出す練習をするか、mapとかを覚えるかをしたほうが良い
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

たかしくんにお遍路を土日だけで巡回させる

たかしくんとは? 算数の文章問題でよく出てくるたかしくんを使って、自分で問題を出して解こうと思います! こちらの記事を見て自分もたかしくんに色々させてみたいと思いました。(たかしくんタグ流行れ) たかしくんのスペック たかしくんは強いので海を渡れます。 たかしくんは強いので山があろうが谷があろうが一定の速度で走れます。 たかしくんは強いので24時間走れます。 ここテンプレートでいいのかも… mdも残しておきます * **たかしくんは強い**ので海を渡れます。 * **たかしくんは強い**ので山があろうが谷があろうが一定の速度で走れます。 * **たかしくんは強い**ので24時間走れます。 お遍路とは お遍路の基本|愛媛のお遍路はこう回る!|愛媛 旅の特集|愛媛県の公式観光サイト【いよ観ネット】 以下抜粋です 約1200年前に弘法大師(空海)が修行した88の霊場をたどる巡礼のこと 参拝する88ヶ寺のことを「札所」と呼ぶ すべての札所を巡拝することで、弘法大師の功徳を得られる 道のりは約1400kmにも及ぶ 参拝は、心を込めて参拝することが何より大切です 4つの必需品がある 正装はこんな感じ (https://www.ippoippodo.com/?mode=f10) たかしくんもお遍路を巡回したいそうです たかしくんはなんか仕事で大きな失敗をしてしまったのか、とりあえずお遍路巡回して徳を得ようと考えたようです。 たかしくんは平日はフルタイム勤務なので土日の48時間で周りたいようです。 リモートワークなので出発(ゴール)するお寺からの移動は考えなくていいようです。 たかしくんはせっかちなので移動は全て直線で行うものとします。 山があったら潜っていくし、谷や海があったら飛んで移動します。 たかしくんはお遍路88箇所を寝ずに食べずに走って周ります。 札所の滞在時間は0秒です。 24時間食べずに走れるので手ぶらでいきます。 走りやすいようにジャージでいきます。 やってみる 他の人が共有しているマップから緯度経度を取得する こちらで公開してくれているマップから緯度経度を取得してみます GoogleMapのAPIを使おうと思いましたが、もっと簡単な方法がありました KMLファイルのダウンロード 直接CSVとかJSONとかダウンロードできれば一番いいですが、いったんKMLというファイル形式でマップの情報をダウンロードします。 KMLからJSONに変換 こちらのサイトでKMLからJSONに変換します。 すると中身が {"type":"FeatureCollection","crs":{"type":"name","properties":{"name":"urn:ogc:def:crs:OGC:1.3:CRS84"}},"features":[{"type":"Feature","properties":{"Name":"第1番札所 霊山寺 りょうぜんじ","description":"第一番札所 竺和山 霊山寺(じくわざん りょうぜんじ)<br>高野山真言宗 <br>ご本尊  釈迦如来<br>開基    行基菩薩<br>御詠歌  霊山の釈迦のみ前にめぐりきてよろずの罪も消え失せにけり<br>住所    徳島県鳴門市大麻町板東霊山寺126<br>電話    086-689-1111<br>北緯34度09分34.0秒東経134度30分10.8秒<br>+34° 9' 34.00\", +134° 30' 10.80\"<br>34.159444, 134.503000","timestamp":null,"begin":null,"end":null,"altitudeMode":null,"tessellate":-1,"extrude":0,"visibility":-1,"drawOrder":null,"icon":null},"geometry":{"type":"Point","coordinates":[134.503,34.159444,0]}},{"type":"Feature","properties":{"Name":"第2番札所 極楽寺 ごくらくじ","description":"第二番札所 日照院 極楽寺(にっしょうざん ごくらくじ)<br>高野山真言宗<br>ご本尊 阿弥陀如来<br>開基   行基菩薩<br>ご詠歌 極楽の弥陀の浄土へ行きたくば 南無阿弥陀仏口ぐせにせよ<br>住所   徳島県鳴門市大麻町檜字段の上12<br>電話   088-689-1112<br>北緯34度09分21.7秒東経134度29分24.9秒<br>+34° 9' 21.70\", +134° 29' 24.90\"<br>34.156028, 134.490250","timestamp":null,"begin":null,"end":null,"altitudeMode":null,"tessellate":-1,"extrude":0,"visibility":-1,"drawOrder":null,"icon":null},"geometry":{"type":"Point","coordinates":[134.490509,34.155566,0]}}, こんな感じでみることができます(データ全部を載せていないです。省略しています) Node.jsで計算していく まずはjsonを読み込んで、緯度経度取得 'use strict'; const fs = require('fs'); const ohenroJson = JSON.parse(fs.readFileSync('./convert.json', 'utf8')); const ohenroArr = ohenroJson.features; ohenroArr.forEach(e => { console.log(e.properties.Name); console.log('lat:' + e.geometry.coordinates[0] + ', lon:' + e.geometry.coordinates[1]); }); 結果は以下のような感じ 距離計算 こちらの記事を参考にまずはデータ的に1番の札所から順番に周ったときの距離を計算してみます 'use strict'; const fs = require('fs'); const ohenroJson = JSON.parse(fs.readFileSync('./convert.json', 'utf8')); const ohenroArr = ohenroJson.features; let sum = 0; for (let i = 0; i < ohenroArr.length - 1; i++) { const cLat = ohenroArr[i].geometry.coordinates[0]; const cLon = ohenroArr[i].geometry.coordinates[1]; const nLat = ohenroArr[i + 1].geometry.coordinates[0]; const nLon = ohenroArr[i + 1].geometry.coordinates[1]; const dist = distance(cLat, cLon, nLat, nLon); sum += dist; } // 直線での距離 console.log('直線でのお遍路巡礼の距離は' + sum + 'km'); // kmを返す function distance(lat1, lng1, lat2, lng2) { lat1 *= Math.PI / 180; lng1 *= Math.PI / 180; lat2 *= Math.PI / 180; lng2 *= Math.PI / 180; return 6371 * Math.acos(Math.cos(lat1) * Math.cos(lat2) * Math.cos(lng2 - lng1) + Math.sin(lat1) * Math.sin(lat2)); } すると結果は 直線でのお遍路巡礼の距離は794.2115810769094km お遍路の参考サイトでは1200kmもの道のりとありましたが、すでに400kmくらい短縮できました さすがたかしくん 土日の48時間でお遍路を直線的に巡回する際、時速何キロで走ればいい? console.log('時速' + (sum / 48) + 'kmで走れば48時間で巡れる'); こちらのコードを追加 答えは、 時速16.546074605768947kmで走れば48時間で巡れる です! 最後に これでたかしくんは時速16.546074605768947kmで48時間ジャージで走り続け、参拝を0秒で行い、土日だけで徳を積むことができたそうです。 めでたしめでたし 最短の組み合わせもやりたかった 全部の組み合わせを出してやろうと思ったけど、処理が1日経っても終わらなかった… ちなみに全部のパターンを配列にしてくれる処理はたぶんメモリリークでだめだった… 88箇所でどこからスタートしてもいいし、ゴールに設定しても良い状況での組み合わせは相当多かった 計算方式として例えばA,B,C,Dの4箇所だとしても ABCD ABDC ACBD ACDB ADBC ADCB Aから始まっているのが6パターンあるのでそれがもう3個あると考えると24パターンあることになるはず。 その計算方法が、4*3*2*1らしい。 なので、組み合わせ数を計算する関数を作ってみた console.log(BigInt(combinationNum(88))); function combinationNum(len) { let r = 1; for (let i = len; i > 1; i--) { r *= i; } return r; } めちゃくちゃでかい数字なのでBigIntで囲ってます 結果 185482642257398395706669786749287093090739583271062212249681965111775306403630728065563094740586576320295195562781717041354470915571712パターンあるっぽいです ここのサイトで漢字の単位を入れると 1854無量大数8264不可思議2257那由他3983阿僧祇9570恒河沙6669極7867載4928正7093澗907溝3958穰3271?622垓1224京9681兆9651億1177万5306 無量大数を普通に単位として使うの初めてだこれ… とにかく無理なので最適化のアルゴリズム、これから勉強してやっていきたいです! JavaScriptでもいいけど、Pythonでもいいかも…
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaScript] 配列の要素を昇順に並べ替えたい

はじめに 実際のフロントエンドの現場で「配列の要素を"昇順"に並べ替えてください」と言われることは結構多くあると思ったんで(たとえば、価格の安い順にデータを表示させたい、といった場面)配列の要素を昇順に並べ替える方法をES6の書き方で簡単にまとめました。 オブジェクトを要素に持たない配列の場合 問題 // この配列を const array1 = [1, 30, 4, 21, 100000]; // 昇順に並べ替えたい [1, 4, 21, 30, 10000] // どうやって実装する? 実装方法 // sort関数は破壊的なメソッドなのでまずは配列をコピーする const cloneArray = [...sampleArray]; const sortedArray = cloneArray.sort((a,b) => a - b ); console.log(sortedArray); // expected output [1, 4, 21, 30, 100000] オブジェクトを要素に持つ配列の場合 問題 // この配列を const items = [ { name: 'tanaka', value: 24 }, { name: 'kimura', value: 35 }, { name: 'suzuki', value: 23 }, ]; // 値順にソートしたい [ { name: 'tanaka', value: 23 }, { name: 'kimura', value: 24 }, { name: 'suzuki', value: 35 }, ]; // どうやって実装する? 実装方法 sort関数のコールバック関数で、valueプロパテを指定して並べ替えればOKです // sort関数は破壊的なメソッドなのでまずは配列をコピーする const cloneItems = [...items]; cloneItems.sort((a,b) => a.value - b.value); console.log(cloneItems); // expected output [ { name: 'tanaka', value: 23 }, { name: 'kimura', value: 24 }, { name: 'suzuki', value: 35 }, ]; 参考文献 Array.prototype.sort() | MDN
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スパム対策:google reCAPTCHAの設置

自分のための忘備録です。 reCAPTCHAとは google reCAPTCHAとはフォーム送信の際に画像認証などを用い、botを防ぐ仕組み。 V2とV3がある。 v2とv3の違い V2・・・画像認証が出てチェックさせる。フォーム送信に手間がかかる。 V3・・・フォームでの挙動から自動でbotか判断する。フォーム送信に手間がかからないが、効いてるのかいまいち分からない。 設置方法 ①まずgoogleアカウントを作り、ダッシュボードでプロジェクトを作っておく 上の「Google Cloud Platform」の横にあるところから「新しいプロジェクト」 ②reCAPTCHAを設置したいドメインを登録して、v2かv3を選んでキーをもらう サイトキーとシークレットキーがある。 v2の設置コード htmlだけで済ませたいならこちら htmlとphpで処理を分けるならこちら v3の設置コード v3設置してて問題点が出てきた どうやらreCAPTCHAはトークンを取得して5分経つとタイムアウトエラー出るみたいです。 タイムアウトエラー対策 タイムアウトエラーだけ無視できるようにちょっと変更します。 ついでに条件を「スコアが0.9以上なら成功」に変更 フォームのphp if (!empty($_POST['recaptchaResponse'])) { $secret = 'シークレットキーを入れる'; $verifyResponse = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret='.$secret.'&response='.$_POST['recaptchaResponse']); $reCAPTCHA = json_decode($verifyResponse); $reCAPTCHA_error = []; if($reCAPTCHA->{'error-codes'}) { $reCAPTCHA_error = $reCAPTCHA->{'error-codes'}; }; if ($reCAPTCHA->score >= 0.9 || (count($reCAPTCHA_error) === 1 && $reCAPTCHA_error[0] === 'timeout-or-duplicate')) { // たぶん人間なので送信とかの処理 } else { // ボットかも } } ただこれだとタイムアウトエラーが出ていたらscore関係なくtrueになってしまいます。 recaptureはタイムアウトエラーになるとscoreが消えてしまうので仕方ないみたいです。 効いてるか確認 参考させていただきました https://norando.net/recaptcha-v3_timeout-or-duplicate/ https://blog.4breaker.com/2020/02/29/post-358/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Calendar上で操作できる予約ソフトを作る [GoogleAppsScript]

はじめに 私の研究室では多くの高価な装置を多人数で共有して使っています。これらの装置に関して予約追加、他人の予約の確認が可能なソフトをGoogle Apps Script(以下GASと呼ぶ)で勉強がてら作ってみました。 初投稿なので改善するべき点などのフィードバック歓迎します。 TL;DR このソフト作成で学んだこと GASのinstallable scriptとcontainer-bound scriptの違い container-bound scriptはboundしているサービス以外のAPIを用いることができないので、一つのサービスのみを用いるソフトに向いている。 GASには単位時間あたりの利用回数制限(quota)がある。 GASにおいて一つのスクリプトで設定できるtriggerは20個までである。 複数のスクリプトで分散する(2個のスクリプトなら40個のtriggerが利用可能)前提で実装するか、20以内に収める努力をすることが必要である。 GASのCalendarAppにおいてはSync tokenという判別子を用いることで、前回の実行後に変更された予定だけに処理を行うことができ、時間の節約が可能となる。 他人の操作に合わせた予定の追加、変更、削除の3つの機能を最も単純に実装する方法は、予定にゲストとして追加する方法である これにより、All day event、詳細、開催場所などの情報をスクリプトで処理する必要がなくなった。 この記事においては詳細の多くを省いているので、詳しく知りたい人は以下のGitHubリポジトリを参照してください。 GitHub repo [equipment-reservation] 使用方法 装置の使用状況(予定の形で保存)をスマートフォンやPCのGoogleカレンダーアプリで確認 アプリで空いている時間帯に予定を追加する形で装置の予約を行う 予約には名前が書いてあり、予定の交渉はSlackや対面で行うことを想定 連絡の機能はこのソフト内にはない ソフトウェアの仕様 仕様に関してはかなり細かく突き詰めた後にプログラムの作成を行った。 概要 Google Apps Scriptというサービスを用いる。これはGoogleのサービスをスクリプトで操作できるサービスで、今回はSheets(表計算)とCalendar(カレンダー)を用いる。 Sheetsはデータベースとして動作し、ユーザーの情報と設定内容を保存している。CalendarはUIとして動作し、予約の追加、変更、確認を行う。 カレンダーにはユーザー側で操作するカレンダー(予約追加および変更用)とサーバー側で操作するカレンダー(予約確認用)が存在する。 前者のカレンダーが操作されたとき、GASが後者のカレンダーに変更を反映する。 以降、前者をWriteカレンダーと呼び、後者をReadカレンダーと呼ぶ。 Writeカレンダー(予約追加および変更用、ユーザー側で操作する) 予定を作成することで、使用する装置、装置の状態、使用時間を指定する。 ユーザー毎にカレンダーが用意されている。 カレンダー名はWrite [User Name]とする。ユーザー名がTana.Y1の場合はカレンダー名はWrite Tana.Y1である。 予定の表記は[device(小文字)] [state]とする。PVDという装置を冷却する場合はpvd coolとする。 Readカレンダー(予約確認用、サーバー側で操作する) 他のユーザーの装置の使用状況を予定としてGoogleカレンダー上に表示する。 ユーザー側の操作方法 情報に疎い人でも問題なく使用できる簡単な操作方法を目標としている。 ユーザーの追加(初期設定) カレンダーを閲覧するGoogleアカウントでGoogle Groupsに加入。 これによりカレンダーの編集権限を得ることができる。 Sheets上で使用する装置にチェックを入れる(一枚目の画像の➂に対応)。 ユーザーのカレンダーを各自のカレンダーアプリに登録する。 Read(読み取り専用)とWrite(書き込み専用)の二つのカレンダーのリンクが用意されている(一枚目の画像の➁では追加できない)。 URL of Calendarというシートを選択し、二枚目の画像のようにリンクをクリックするとGoogle Calendarが立ち上がり、カレンダーを追加できる。 マウスカーソルをリンク上に移動してカレンダー上に追加する。 予定の追加 Calendar上で予定を追加する。 予定のタイトルには[装置名] [状態]を記入する(画像の➀に対応)。 装置名はSheetsの一行目で設定されており、状態は任意の値の入力が可能である。 追加するカレンダーはWrite [User Name]を選択する(画像の➁に対応)。 プログラムの動作 概要 ユーザーがカレンダー上で予約を追加 GASがカレンダーの予定の変化を検知 Calendarの予定編集時のトリガーを用いる。 変化した内容をみて、その装置を使用するユーザーの読み取り専用カレンダーに予定を表示する。 予定が追加された場合は読み取り専用カレンダーに予定を追加する。 予定が変更された場合は変更前の予定は消え、変更後の予定が読み取り専用カレンダーに表示されるようにする。 予定が削除された場合は読み取り専用カレンダーから予定を削除する。 動作の実現方法と大事な点 Standalone scriptを用いている。トリガーはinstallable triggerを用い、Sheets上での変更を検知する。 Container-bound scriptを用いなかったのは、Sheetsにboundした場合はCalendarのAPIを呼び出す権限がなくカレンダー名を変更できないため、今回の目的を達成できないためである。 カレンダーは事前に作成する必要がある。スクリプト一つにつき設定できるトリガーは20個であるため、ユーザー19人分の読み取り専用、書き込み専用カレンダーを作成し、書き込み専用カレンダーにトリガーを設定した。残りの1個のトリガーはsheetsに設定した。人が入れ替わる場合はカレンダーを再利用することを想定している。 全員の予定を確認することのできるカレンダーはALL.U1というユーザー名で作成した。 このスクリプトは次に示すようないくつかの役割を担っている。 ユーザーの追加、変更 ユーザーの追加、変更によるカレンダー名の変更 ユーザーの追加、変更による予定内のユーザー名の変更 ユーザーによる予定の追加、変更、削除 ユーザーが読み取り専用カレンダーで閲覧する装置の変更 1. ユーザーの追加、変更 Sheets上に名前を追加、変更する。名前の表記はローマ字表記で行い名、姓の順で表記し間にスペースをはさむ。 Sheets上での変更をトリガーが検知する。 名前を名と姓に分解して、[姓の最初の四文字].[名の最初の文字]をUser Name 1として求める。 すべてのユーザーのUser Name 1を見て上の行から順に重複防止用の番号をつけていき、User Name 2とする。これを以降ユーザー名(User Name)として採用する。User Name 2の表記は[姓の最初の四文字].[名の最初の文字][1,2,3,...]のようになる。 2. ユーザーの追加、変更によるカレンダー名の変更 ユーザーの追加、変更が行われた後にカレンダー名の変更が行われる。 Sheetsの行ごとにカレンダーが二つ用意されている。これは読み取り専用と書き込み専用カレンダーに対応している。それぞれのカレンダーの名前をそれぞれRead [User Name]とWrite [User Name]に変更する。 3. ユーザーの追加、変更による予定内のユーザー名の変更 ユーザーの追加、変更が行われた後にカレンダー名の変更が行われ、そのあとに予定内のユーザー名の変更が行われる。 4. ユーザーによる予定の追加、変更、削除 Sync tokenという判別子を用いることで、変更された予定だけに処理を行う。これにより時間の節約が可能となる。 追加、変更、削除の3つの機能を最も単純に実装する予定の追加方法として読み取り専用カレンダーをゲストとして予定に追加する方法を用いた。これにより、ゲストに追加された読み取り専用カレンダーから予定を見ることができる。 予定追加の場合は読み取り専用のカレンダーを予定のゲストとして追加する形で予定を追加する。 予定変更の場合は、読み取り専用のカレンダーはすでに予定のゲストとして追加されてているため、もう一度ゲストとして追加しても変化はない。また、変更は自動的に読み取り専用カレンダーに適用される。 予定削除の場合は、読み取り専用のカレンダーがゲストとして参加している元の予定が削除されるとその予定は読み取り専用のカレンダーから削除される。 5. ユーザーが読み取り専用カレンダーで閲覧する装置の変更 閲覧する装置を変更した場合は、過去10日間にさかのぼり、閲覧するとして設定した装置の予定を追加する。前に閲覧していたが、今回閲覧しないと設定した装置に関しては予定を削除する。予定の追加、削除に関しては前と同様にゲストとして読み取り専用カレンダーに参加させることで追加する。 installable triggerのquotaの確認 Google Apps Scriptには以下のquota(利用回数制限)が設定されている。19人のユーザーがいたとしても十分な実行回数が確保できると考える。 カレンダーの予約の作成:一日当たり5,000回実行可能 triggerの実行時間:一日当たり90分間実行可能 1回の実行時間を15秒とすると一日当たり360回実行可能 Quotaには書いていないが、連続での予定の作成の間隔が短すぎると、予定の作成を拒否される。したがって、予定の作成の間隔を1sと設定した。 付録 ユーザー名の表記 ユーザー名は重複がないように以下のような形式になっている。数字は、姓名に重複のない場合は1であり、重複があった場合は後に作ったユーザーの数字を一つずつ増加させる。 [姓の最初の4字(最初の文字は大文字)].[名の最初の一文字(大文字)][1,2,3,...] ソフトウェアで用いる省略名称 スマートフォンのカレンダーアプリで表示することのできる文字数は制限されているため、装置名、状態、ユーザー名を省略表記で表示する。なお、装置の状態名は基本的に入力の簡単化のため、小文字になっている。装置名、ユーザー名はユーザーが入力することは基本的にはないため、大文字を用いている。 - Devices (Sheets上で装置名の設定が可能) Device Description cmp Chemical mechanical planarization rie Reactive ion etching cvd Chemical vapor deposition pvd Physical vapor deposition euv Extreme ultraviolet lithography diff Diffusion cleg Cleaning dicg Dicing Pack Packaging States of equiment(これに限らず任意の値を用いることができる) State 説明 evac evacuation use/[NO ENTRY] Main operation cool cooling Users (Sheets上でユーザーの追加可能) User Name 名前 Tana.Y1 Tanaka Yuusuke Tana.Y2 Tanabe Yuta Tana.S1 Tanahashi Shion Chen.F1 Chen Fan Zhu.Y1 Zhu Yu
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gatsby-microCMS連携

Gatsbyではいろいろなツールをデータソースとして使うことができます。 いろいろとでまわっているヘッドレスCMSを使うことができるようです。 英語ベースの物を中心に、スターターもあるので、これがあるとお手軽に連携が可能です。 日本製のヘッドレスCMSとして注目を浴びているmicroCMSですが、 なかなか使い勝手もよいようで、使えるようになりたいと思って 練習で設定をしてみました。 公式スターターが見当たらなかったので、やってみた過程を記事にしてみようと思います。 Gatsbyサイトを構築して、連携させるところまでのメモです。 microCMS設定、サンプル記事作成 ユーザー登録からサービスを作成、APIのエンドポイントの設定 ユーザー登録の流れで、サービスの作成、APIエンドポイントの設定などがあります。 サービスというのが大きな単位で、 この中にいくつかのデータベースを作成することができます。 無料の「ホビープラン」なので、サービスは1つだけ作ることができます。 今回は練習用に horumontというサービスの中に ブログとお知らせという2つのデータベースを作成します。 API名とエンドポイントを設定します。 すでに構築したあとで画像をとったので、API名とエンドポイントが解説とちがっていますが、それぞれ必要な名前を設定します。エンドポイントの名前をのちほどGatsbyの設定ファイルで使用します。 次へ進むと形式の選択画面になります 今回はリスト形式を選択しました。選択して次へすすみましょう。 APIスキーマを定義 データベースの項目を決めていきます。 項目のID、項目の表示名、データ形式を指定していきます。 今回は 項目のID / 表示名 / データ形式 title / タイトル / テキストフィールド link / リンク先 / テキストフィールド eye-catch / アイキャッチ画像 / 画像 article / 記事内容 / リッチエディタ としました。 名前つけがプログラミングの世界では一般的なものになっていないかもしれません。気づいた人はよかったらご指摘ください。 このあたりの説明も日本語で書いてくれているのでありがたいです。 2つ目以後は「コンテンツ(API)」の横の「+」ボタンから追加できます。 Gatsbyサイト構築 gatsby new 1.なにはともあれ新しいサイトを構築をします。 今回は、定番のhallo-world starterを使用します。 スターターはこちら>>> gatsby new プロジェクトの名前 https://github.com/gatsbyjs/gatsby-starter-hello-world 2.hello worldを見れるか確認しておきましょう // ターミナルでプロジェクトフォルダに移動します。 cd プロジェクトの名前 // 開発サーバーの起動 gatsby develop ブラウザで、localhost:8000にアクセスして 超シンプルなhello worldの画面を確認できたらOK プラグインのインストール microCMSと接続するための gatsby-source^microcmsという プラグインをインストールします。 公式のプラグインのページはこちらです ターミナルで次のどちらかを実行します。 npmかyarnか。 パッケージマネージャ(いろいろなプログラムをまとめたものを管理するプログラム)はどちらがいいのか 永遠の課題です。今回ぼくはyarnを使いました。 // パッケージマネージャnpm npm install gatsby-source-microcms // パッケージマネージャyarn yarn add gatsby-source-microcms microCMSとの接続設定 microCMSと接続するためのAPIキーなどの設定をします。 これらの設定値は、プログラムに直接書いてしまうと誰でも見ることができて だれでもデータベースのアクセスできることになっていしまいますので、 「環境変数」という仕組みを使います。 □1 環境変数を使うためのプラグイン「dotenv」をインストール yarn add dotenv □2 gatsby-config.jsを編集 // dotenvを使うための設定 require('dotenv').config({ path: `.env.${process.env.NODE_ENV}`, }); module.exports = { // サイトのメタ情報の設定 これはあとでもOKですが、サイトのSEO情報なのでやってしまうことにします。 siteMetadata: { title: "microcms-lesson", // サイトのタイトル:ブラウザのタブのところに出てきます。 description: "This is Desc of this site!", // サイトのちょっとした説明 }, // プラグインの設定 gatsby-source-microcmsの設定 plugins: [    { resolve: 'gatsby-source-microcms', options: { apiKey: process.env.MICROCMS_API_KEY, serviceId: process.env.MICROCMS_SERVICE_ID, apis: [ { endpoint: 'posts', // micorCMSで作ったapiのエンドポイントを記載 }, { endpoint: 'news', // 複数作るときは{}で囲って指定します。 } ], }, }, ], }; graphQLで接続を確認 gatsby developで開発サーバーを起動 localhost:8000___graphqlにアクセス VSCode内のターミナルだと、最後の方にリンクが出てくるのCommand+enterでアクセスできます。 graphiQLにアクセス graphqlを操作するためのインターフェースgraphiQLが起動します。 左のexploreのところに microcms○○○というクエリが確認できればOK 接続が確認できたので、あとは、graphQLを通してデータを頂くだけ。 今日の記事ではここまでとします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】{{ mix() }}とは?JavaScriptファイルを読み込む最適な方法(Laravel Mixの活用)

LaravelでJavaScriptファイルを読み込む場合に、Laravel Mixを使うと軽量化したファイルを読み込むことができる。 Laravel MixとはLaravelの中でWebpackを使ってsass, scc, javascriptファイルなどをコンパイルできるAPIのこと。 ブレードの中で以下の記述があった場合はLaravel Mixでコンパイルしたjavascriptファイルを読み込んでいる。 <script src="{{ mix('ファイルパス') }}"></script> 目次 Laravel Mixのインポート コンパイルの設定(Webpack.mix.js) コンパイル対象ファイルの編集 コンパイル対象のjsファイルでimportする コンパイル対象のファイルを直接読みこむ globを使ってコンパイル対象をワイルドカードで指定する コンパイルの実行 ブレードで呼び出し (補足)assetヘルパを使った通常の読み込み Laravel MixでJSファイルコンパイルしてビューで使う方法 1. Laravel Mixのインポート Laravelのプロジェクトを起動したときに、package.jsonに記載されているので、インポートする。 #nodeとnpmの存在確認 ##インストールされてない場合はインストール node -v npm -v npm install pacakge.jsonに記述してあるライブラリをインストール。 2. コンパイルの設定(Webpack.mix.js) Webpackのコンパイル設定が書かれたWebpack.mix.jsを編集する。 Javascriptのコンパイル設定は以下のように記述する。 ・mix.js('コンパイル対象のjsファイルパス', 'コンパイル後のファイル出力先'); Webpack.mix.js const mix = require('laravel-mix'); mix.js('resources/js/app.js', 'public/js'); resources > js > app.jsファイルをコンパイルして、public > jsディレクトリ配下にapp.jsとして出力する。 3. コンパイル対象ファイルの編集 対象のJavascriptファイルを読みこむ方法はいくつかある。 3-1. コンパイル対象のjsファイルでimportする ブレードで読み込む対象ためのファイルとしてscritpt.jsをresources > jsディレクトリ配下に作成する。 コンパイル対象はapp.jsのため、このファイルの中でインポートする。 app.js import "script.js" 3-2. コンパイル対象のファイルを直接読みこむ コンパイル対象のファイルを直接読み込むようにすることもできる。 Webpack.mix.js const mix = require('laravel-mix'); mix.js('resources/js/app.js', 'public/js') .js('resources/js/script.js', 'public/js'); 3-3. globを使ってコンパイル対象をワイルドカードで指定する Webpack.mix.js const mix = require('laravel-mix'); const glob = require('glob'); glob.sync('resources/js/*.js').map(function(file) { mix.js(file, 'public/js').version() }); 詳細はglobの使い方を参照。 4. コンパイルの実行 npm run dev もしくは以下を実行しておくと、変更を検知して自動コンパイルしてくれる。 npm run watch 5. ブレードで呼び出し </body>の直前に以下を記述する。 ・<script src="{{ mix('ファイルパス') }}"></script> ファイルパスはresourcesディレクトリ配下を指定する。 ▼resources > js > script.jsの場合 <script src="{{ mix('js/script.js') }}"></script> Laravel Mixを使ってブレードの中でJSファイルを呼び出す処理が完了。 (補足)assetヘルパを使った通常の読み込み Laravel Mixを使わずに、通常通りpublicディレクトリ配下にあるリソースを読み込む場合はassetヘルパを使う。 ▼public > js > app.js を読み込む場合の例 <script src="{{ asset('js/app.js') }}"></script> 以上。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Lazyload】lozad(ロザド)を使った遅延読み込みの簡単実装方法

ページの表示速度改善のために画像や動画などのリソースをスクロールが達したタイミングで読み込み表示する遅延読み込み(Lazyload)を簡単に実装できるlozadの使い方について。 目次 Lozadとは? Lozadの使い方 lozadの読み込み タグに適用する imageタグへの適用例 pictureタグへの適用例 iframeタグへの適用例 videoタグへの適用例 背景画像への適用例 レスポンシブ対応方法 デフォルトのクラス名(lazad)を変更する方法 Lozadとは? リソースの遅延読み込みを簡単に実装できる。 javascriptで書かれた軽量ライブラリ。 様々なタグに対応している(img, picture, iframe, videos, audios) レスポンシブ画像や背景画像などにも対応。 画像の代替表示ができる(ダミーの軽量な画像を表示しておく)。 Lozadの使い方 1. lozadの読み込み lozadの読み込み方法は2つある。 CDNを使う パッケージをインストールする 1-1. CDNで読み込む headタグ内に記述。 <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/lozad/dist/lozad.min.js"></script> 1-2. パッケージをインストールする パッケージをインストールし、読み込む。 インストール $ npm install lozad インストールはyarnでもできる。yarn add lozad 1-1-2. 読み込み 読み込み(ES6の場合) import lozad from 'lozad' const observer = lozad(); //デフォルトのクラス名はlazad。引数で好きなクラス名を指定できる。 observer.observe(); または 読み込み(CommonJSの場合) var lozad = require('lozad') const observer = lozad(); //デフォルトのクラス名はlazad。引数で好きなクラス名を指定できる。 observer.observe(); ES6とCommonJS(CJS)の歴史はこちらの記事がわかりやすいです。 1-1-3. HTMLにインポート lozadを使用したいページの中で上記内容を読み込む。htmlの下部</body>の上に記述する。 <script src="ファイルパス"></script> または直書き(おすすめしません、、) <script> import lozad from 'lozad' const observer = lozad(); observer.observe(); </script> 以上でlozadを使う準備が完了。 2. タグに適用する lozadの基本構文で使用する属性は2つ。 class="lozad" data-src="リソースのパス" class="lozad" ※デフォルトのクラス名の場合 data-src="遅延読み込みするリソースのパス" src="ダミー画像のパス"(なくてもいい) 遅延読み込みしたいリソースにクラス名lozadをつけ、読み込む画像をdata-srcに記述する。 もし、ページの初期ロードのタイミングでダミー画像(軽量な画像など)を表示しておきたい場合はsrcに記述する。 2-1. imageタグへの適用例 image.pngを読み込む場合 通常 <img src="image.png"> ↓ 遅延読み込みの適用 lozad適用(これの記述でOK) <img class="lozad" data-src="image.png"> ↓ 初期にダミー画像 dummy.png を表示させておく場合 lozad適用(ダミー画像あり) <img class="lozad" data-src="image.png" src="dummy.png"> 2-2. pictureタグへの適用例 ダミー画像の有無で記述方法が異なる。 ダミー画像を設置しない場合 ダミー画像を設置する場合 (参考) pictureタグの基本的な使い方 2-2-1. ダミー画像を設置しない場合 sourceタグのどの条件にも当てはまらない場合に表示するimageタグを記述しない。 代わりに、pictureタグの中で画像を指定する。その際、最少の高さも指定しておく。 <picture class="lozad" style="display: block; min-height: 1rem" data-iesrc="image.jpg" data-alt=""> <source srcset="image-large.jpg" media="(min-width: 1280px)"> <source srcset="image-middle.jpg" media="(min-width: 980px)"> <source srcset="image-small.jpg" media="(min-width: 320px)"> </picture> alt属性はdata-altに記述する。 実際に使う場合は、pictureタグに以下を追記して、imageタグにnoscriptタグ記述すれば、lozadの付け外しが簡単にできる。 class="lozad" style="display: block; min-height: 1rem" data-iesrc="条件に当てはまらない時の画像パス" data-alt="" <picture class="lozad" style="display: block; min-height: 1rem" data-iesrc="条件に当てはまらない時の画像パス" data-alt=""> <source srcset="image-large.jpg" media="(min-width: 1280px)"> <noscript><img src="条件に当てはまらない時の画像パス" alt=""></noscript> --> </picture> 2-2-2. ダミー画像を設置する場合 スクロールが達するまでダミーで表示しておく画像を設定する場合はimageタグに記述する。 <picture class="lozad" style="display: block; min-height: 1rem" data-iesrc="条件に当てはまらない時の画像パス" data-alt=""> <source srcset="image-large.jpg" media="(min-width: 1280px)"> <img src="ダミー画像のパス"> </picture> pictureタグの中のimageタグの用法が通常と異なることに注意が必要。 2-3. iframeタグへの適用例 imageタグへの適用と同じ。 <iframe data-src="リソースのパス" class="lozad"></iframe> 2-4. videoタグへの適用例 imageタグへの適用と同じ。 <video class="lozad" data-src="video/mov.mp4"> 再生オプションを指定する場合は以下属性を追加する。 属性 内容 autoplay 自動再生 muted デフォルトでミュート loop 繰り返し autoplay playsinline スマホで勝手に全画面表示にしない controls コントロールパネルの表示 poster="画像パス" 動画を再生するまでに表示する画像 preload=”none” 事前読み込みしない 記述例 <video class="lozad" data-src="video/mov.mp4" autoplay muted loop playsinline controls data-poster="thumnail.jpg"> source属性を使う場合 source属性を使って環境に合わせて再生する動画を選ぶ場合は以下のようになる。(基本mp4再生できるので不要) <video class="lozad" data-poster="images/backgrounds/video-poster.jpeg"> <source data-src="video/mov_bbb.mp4" type="video/mp4"> <source data-src="video/mov_bbb.ogg" type="video/ogg"> </video> posterは動画を再生するまでに表示する画像。lozadを適用する場合はdata-posterとする。 2-5. 背景画像への適用例 <div class="lozad" data-background-image="image.png"> </div> 3. レスポンシブ対応方法 タグの中で画像幅に合わせて表示するリソースを指定することができる。data-srcsetを使う。 data-srcset="画像パス1 条件, 画像パス2 条件" srcset属性はHTML5.1から追加された属性で、imageタグやpictureタグ内のsourceタグで使うことができる。 幅の指定はw(画面のピクセル幅)かx(デバイスピクセル比)かvw(画面幅に対する割合)で指定する。 実例 <img class="lozad" data-src="image-3x.png" data-srcset="image.png 1000w, image-2x.png 2000w"> 画面の幅が1000pxまではimage.pngを表示。 1001px~2000pxまではimage-2x.pngを表示。 どの条件にも当てはまらない(2001px以上)はimage-3x.pngを表示。 pictureタグを使って条件を指定して出し分けるのもあり。 4. デフォルトのクラス名(lazad)を変更する方法 デフォルトのクラス名lozadは変更することができる。 lozadを読み込んだjsファイルで、lozadの引数で好きなクラス名を指定する。 .js import lozad from 'lozad' const observer = lozad('.適用したいクラス名'); observer.observe(); 実例 jsファイルの設定 lazyload.js import lozad from 'lozad' const observer = lozad('.is-lazyload'); observer.observe(); ↓ htmlに適用 .html <img class="is-lazyload" data-src="image.png"> <script src="public/js/lazyload.js"> </body> 以上。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gatsbyで動画自動再生に手こずった(mp4)

サイトによくあるトップページの動画自動再生・・・ なかなか動かなくて調べました。 JSXとの兼ね合いで良い記事が見つからず、あれこれ試したが、 <vide src="/images/~~~~~~~.mp4"></video>では動かなかった。 <video autoPlay loop muted playsInline controls style={{ width:"100%" }}> <source src="/images/~~~~~~~.mp4" /> </video> <source>タグを作って内側に書くという方法。 レスポンシブにするかはstyle={{ width:"100%" }}の部分をつけるか外すか。 ※ここのHTMLなどをJSX記法に変換してくれるオンラインツール便利すぎた!!! HTML to JSX Compiler
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jqueryでつまづき中・・・やや限界に近い

階層リストのチェックボックス いまとあるアプリのチェック付きリストを実装しようとしています。 パタパタと押し開くようなボタンです。 やりたいこと 孫から親をさかのぼって、チェックボックスをすべて入れたい (BEFORE) □朝ごはん   □味噌汁     □わかめ □昼ごはん   □ラーメン     ■わかめ    □夜ごはん   □スープ     □わかめ (AFTER) □朝ごはん   □味噌汁     □わかめ ■昼ごはん   ■ラーメン     ■わかめ   □夜ごはん   □スープ     □わかめ source 親から子供に行くソースは多々ある。 しかし、逆に下から上っていくものが見つからない あとラーメンを押したとき、わかめには入らないこと なかなかうまくいかへんなあ・・・ Script とりあえず、自分でやりかけてみる・・・ (またしても、誰かにお助けしてもらえないかなと期待する) 追記 わかめの親が、いつまでたっても味噌汁のまま teratailに投稿  https://teratail.com/questions/332516?modal=q-comp $(this).parent().parent().find('label').html() "わかめ" $(this).parent().parent().parent().find('label').html() "味噌汁" $(this).parent().parent().parent().parent().parent().parent().parent().find('label').html() "味噌汁"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【個人開発】制限のない自由なSNS作ってみた!

初めに この度Ammotというwebアプリを開発しました。 コンセプトは「制限の少ない自由な投稿を」です。 「ツイッターは文字数制限がきつい、ただfacebookは実名制だしデザインがごちゃごちゃでいや」 という声を聴いたので作ってみました。 先に行っておくと Ammotは文字数制限が6000字まで、画像・動画・PDF・音声は同時に10個まで投稿可能です。 概要 ↑ログイン前のトップページにはフィットスクロール(名前間違いかも)を使用して新しいおしゃれなサイト感を出してます。 ↑これがログイン後のトップです。フォローしたユーザーの投稿がHOMEに流れて、 サイドバーにはおすすめの投稿と現在人気のタグを表示しています。 ↑ポップアップの投稿画面です。 ツイッターのいやなとこ 文字数制限 編集ができない(個人的にめちゃ嫌) facebookのいやなとこ 制限はないがデザインがぐちゃぐちゃ 実名制はなんかいやだ この二つSNSの問題を解決するのがAmmotです。 工夫した点 無料プランにこだわった こういうものはバズらない限り、すぐには成功しません。使用者が全くいなくても1年は公開しようと思います。 でも使用者がいないのに月数百円も取られるの馬鹿馬鹿しいじゃないですか。だからドメイン以外はすべて無料です。 ただherokuの無料プランだとドメインを設定できないのでCloudflareを使い設定しました。 参考記事: デザインとUIにこだわった(つもり) フィットスクロール?だったり投稿画面のポップアップだったり 今まで使ってこなかった手法を使ってみました。 自己満になってるかもしれない ちなみにデザインを参考にしたサイトはtumblrです。 比べればまぁまぁ似てると思います。 機能性 最初にも書きましたが画像・動画・PDF・音声の投稿とプレビュー機能を実装しました。 これにはいろんな記事がありましたが まともに動きかつ一番簡単でコードもシンプルなこの記事を参考にするのをお勧めします。 使用した技術 rails6 ruby2.7 postgresql heroku free aws s3 gem 'ridgepole' gem 'slim-rails' gem 'html2slim' gem 'pry-rails' gem 'devise' gem 'kaminari' gem 'activeadmin' gem 'rack-attack' gem 'rails-i18n' gem 'devise-i18n' gem 'devise-i18n-views' gem 'carrierwave' gem 'rmagick' gem 'rinku' gem 'fog-aws' gem 'dotenv-rails' #一部抜いてます まとめ 今回の開発は今までやらなかったことにめちゃ挑戦できたので楽しかったです 苦手なjquery・javascriptもプレビュー機能のおかげでちょっとわかった気がしてます。 文字数制限や実名制で不便を感じたことのある人はぜひAmmot使ってください!お願いします。 ↓URL https://ammot.net/ ↓僕のAmmotのアカウント https://ammot.net/user/yamada ↓僕のツイッターのアカウント https://twitter.com/yamada1531
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

制限の少ない自由なSNS

初めに この度Ammotというwebアプリを開発しました。 コンセプトは「制限の少ない自由な投稿を」です。 「ツイッターは文字数制限がきつい、ただfacebookは実名制だしデザインがごちゃごちゃでいや」 という声を聴いたので作ってみました。 先に行っておくと Ammotは文字数制限が6000字まで、画像・動画・PDF・音声は同時に10個まで投稿可能です。 概要 ↑ログイン前のトップページにはフィットスクロール(名前間違いかも)を使用して新しいおしゃれなサイト感を出してます。 ↑これがログイン後のトップです。フォローしたユーザーの投稿がHOMEに流れて、 サイドバーにはおすすめの投稿と現在人気のタグを表示しています。 ↑ポップアップの投稿画面です。 ツイッターのいやなとこ 文字数制限 編集ができない(個人的にめちゃ嫌) facebookのいやなとこ 制限はないがデザインがぐちゃぐちゃ 実名制はなんかいやだ この二つSNSの問題を解決するのがAmmotです。 工夫した点 無料プランにこだわった こういうものはバズらない限り、すぐには成功しません。使用者が全くいなくても1年は公開しようと思います。 でも使用者がいないのに月数百円も取られるの馬鹿馬鹿しいじゃないですか。だからドメイン以外はすべて無料です。 ただherokuの無料プランだとドメインを設定できないのでCloudflareを使い設定しました。 参考記事: デザインとUIにこだわった(つもり) フィットスクロール?だったり投稿画面のポップアップだったり 今まで使ってこなかった手法を使ってみました。 自己満になってるかもしれない ちなみにデザインを参考にしたサイトはtumblrです。 比べればまぁまぁ似てると思います。 機能性 最初にも書きましたが画像・動画・PDF・音声の投稿とプレビュー機能を実装しました。 これにはいろんな記事がありましたが まともに動きかつ一番簡単でコードもシンプルなこの記事を参考にするのをお勧めします。 使用した技術 rails6 ruby2.7 postgresql heroku free aws s3 gem 'ridgepole' gem 'slim-rails' gem 'html2slim' gem 'pry-rails' gem 'devise' gem 'kaminari' gem 'activeadmin' gem 'rack-attack' gem 'rails-i18n' gem 'devise-i18n' gem 'devise-i18n-views' gem 'carrierwave' gem 'rmagick' gem 'rinku' gem 'fog-aws' gem 'dotenv-rails' #一部抜いてます まとめ 今回の開発は今までやらなかったことにめちゃ挑戦できたので楽しかったです 苦手なjquery・javascriptもプレビュー機能のおかげでちょっとわかった気がしてます。 文字数制限や実名制で不便を感じたことのある人はぜひAmmot使ってください!お願いします。 ↓URL https://ammot.net/ ↓僕のAmmotのアカウント https://ammot.net/user/yamada ↓僕のツイッターのアカウント https://twitter.com/yamada1531
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

未経験がWeb制作エンジニアを目指す④

まぁつまらないタイトルですねそれにしても。私なら見かけても絶対に読もうと思わないです。 学習内容と所見 Udemy:ウェブ開発入門完全攻略コース - HTML/CSS/JavaScript. プログラミングをはじめて学び創れる人へ! JavaScript編が終わ・・・りませんでした。 for文if文のような基礎的な内容だったので「ふ~ん」と流して見ていたせいか演習問題で間違えました。 「1~100内の偶数を足し合わせろ」的な問題だったのですが、1~100内の偶数を全部表示する処理をしてしまい、どれだけ文章をよく読んでいないかを痛感しました。仕事なら上司キレさせてました。 今後の予定 引き続きJavascript編、その後のJavascriptDOM編も終わらせたいです(希望的観測)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む