logo
webtech

Canvas上に線を引くイベントをtouchmoveにつけると、スマートフォンでピンチイン・ピンチアウトなどをしようとした時に、線が引かれるようになってしまう。
ウェブ開発をしたことある人なら、Canvas外で操作すれば、問題ないことを知っているだろうけど、普通はそういうことには気がつかないことが大半だろうと思われる。
利用者にとってCanvasを使いやすくするためには、その点も配慮したほうが使いやすくなるはずである。

実装例

<div id="wrap"> <canvas id="canvas" /> </div> <style> #wrap { width: 100vw; height: 100vh; position: relative; } #canvas { position: absolute; width: 500px; height: 200px; top: 0; bottom: 0; right: 0; left: 0; margin: auto; } </style>
const canvas = document.getElementById('canvas'); const canvasWrapper = document.getElementById('wrap'); // @var {Bool} isDrawing 描画可能か let isDrawing = false; canvasWrapper.addEventListener('touchstart', (event) => { if ( event.touches.length === 1 && document.elementFromPoint(event.touches[0].clientX, event.touches[0].clientY) === canvas ) { isDrawing = true; } else { // canvasWrapperとcanvasの境目辺りをタッチした場合、稀にスクロールイベントが先に発火してしまうことがある。 // それを抑制するために、イベントの伝播を止める event.stopPropagation(); isDrawing = false; } }); canvas.addEventListener('touchmove', (event) => { if (!isDrawing) { return; } event.preventDefault(); // ここに描画処理 });

touchstart イベントは、canvasではなく親要素に付けているところがポイント。

CakePHPにおいてモデルの各イベント間で値を共有したい場面がたまにある。
たとえば、 beforeSave でしか取得できない値を afterSave で使いたいとか、エンティティの値によって buildRules のルールに値を渡したいなど。

そういう場合、以下の方法で値の受け渡しができるようになる:

// モデル内で public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) { $options['hogehoge'] = 'hugahuga'; } public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options) { debug($options['hogehoge']); // => 'hugahuga'; }

各イベントの $options はイベント間で同じものが使いまわされているため、先に発生するイベントで追加された値は、後のイベントで利用できるようになる。
上記の例では、 beforeSave で追加した $options['hogehoge']afterSave で利用できるようになっている。

また、この $optionsルールチェッカー$rules->add の引数としても利用されているため、そこでも利用できる。

public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) { $options['foo'] = true; } public function buildRules(RulesChecker $rules) { $rules->add(function ($entity, $options) { debug($options['foo']); // => true; }, 'ruleName'); return $rules; }

上記の例では、単純に固定値を設定しているだけだが、複数のルールで同じモデルの同じレコードを参照しないといけない場合など、あらかじめ beforeSave などで、その共通で参照しないといけないデータを取得して $options に追加しておけば、ルール毎にfindする必要がなくなり、多少は高速化が見込める。
同じコードを何度も書く必要がなくなり、見通しがよくなるはず。

idnameemail
1hogeexample.hoge@example.com
2hugaexample.huga@example.com
3piyoexample.piyo@example.com

このようなテーブル( users とする)があった時、次のコードで id を一次元配列で一発で取得できる:

$result = TableRegistry::getTableLocator()->get('Users')->find()->select('Users.id')->extract('id')->toList(); // $result = [1, 2, 3]

email だけを取得したい場合も同様:

$result = TableRegistry::getTableLocator()->get('Users')->find()->select('Users.email')->extract('email')->toList(); // $result = ['example.hoge@example.com', 'example.huga@example.com', 'example.piyo@example.com']

このコードは CakePHP3 以上であれば動くはず

スマホやタブレットでタッチ操作をしている場合、 DragEvent が使えないため、 TouchEvent を利用して擬似的にドラッグ&ドロップを実現する必要がある。
本記事はそのヒント。

まずはじめに、ドロップイベントを実現するには、 touchend イベントと document.elementFromPoint を組み合わせればよい。 touchend イベントが発火した場所の要素を document.elementFromPoint で取得し、その取得できた要素がなにかを判定して、すきな処理を行えばよい。

例:

/** * @param {Event} event イベントオブジェクト * @return {Element|null} */ const getElementFromEventPoint = (event) => { const { clientX, clientY } = /^touch/.test(event.type) ? event.changedTouches[0] // touchstart|touchmove|touchendの場合はこちらから座標を取得 : event; // その他(マウスやポインタ)のイベントの場合は、こちらから座標を取得 return document.elementFromPoint(clientX, clientY); // 座標から要素を取得 } let isDragging = false; window.addEventListener('touchstart', () => { isDragging = true; }); window.addEventListener('touchend', (event) => { if (!isDragging) { return; } isDragging = false; const el = getElementFromEventPoint(event); // イベントが発生した座標の要素に `data-drop-zone` 属性が付与されていたらなにかしら処理する if (el && typeof el.dataset.dropZone !== 'undefined') { // 処理... } });

この例では、 touchend でドロップを再現しているけれど、同じ要領で touchmovedragover を再現したりすることが可能。

https://codepen.io/riffrain/pen/yLLWYgq の様に、よくあるドラッグ&ドロップを簡単にするパッケージと組み合わせたり、それの拡張( https://codepen.io/riffrain/pen/yLLWYgq では、本来ドロップできない外の要素でドロップを検知している)をすることができる

オブジェクトをフラットにしたり、フラットなオブジェクトを元に戻したりするJS
lodashzipObjectDeep に依存
CakePHPのHash::flatten のようなもの
zipObjectDeep で展開するため、キーの構成は Hash::flatten とは異なる

import _ from 'lodash'; const numberRE = /^(?:0|[1-9][0-9]*)$/; const flatten = (originalData) => { const result = {}; const stack = []; let paths = []; let data = JSON.parse(JSON.stringify(originalData)); while(true) { data = { ...data }; const key = Object.keys(data).shift(); if (key) { const element = data[key]; delete data[key]; if (element !== null && typeof element === 'object' && Object.keys(element).length) { if (data) { stack.push([data, [...paths]]); } data = element; if (numberRE.test(key)) { if (paths.length && paths[paths.length - 1] === '.') { paths.pop(); } paths.push(`[${key}]`); } else { paths.push(key); } paths.push('.'); } else { result[`${paths.join('')}${key}`] = element; } } if (!Object.keys(data).length) { if (stack.length) { [data, paths] = stack.shift(); } else { break; } } } return result; } const expand = (data) => { const keys = []; const values = []; const dataKeys = Object.keys(data); let key = dataKeys.shift(); while(key) { keys.push(`result.${key}`); values.push(data[key]); key = dataKeys.shift(); } const { result } = _.zipObjectDeep(keys, values); return result; }
  1. Prismicで用意されているサンプルを動く環境を構築する
    https://prismic.io/docs/technologies/sample-blog-with-prismic-and-gatsby を参考にPrismicとGatuby.jsの設定をする
    ※2021/01/23時点、リンク先に通りに作成したらgatsby-node.js 等でエラーが出るので自分で解消する必要がある

  2. Netlifyに設定を追加する
    基本的には、GatsbyCloudの設定と同じで、環境変数やWebhookの設定を適宜NetlifyのものにすればOK