HTML
<input id="input" placeholder="HH:mm 〜 HH:mm" />
Javascript
let result = '';
new Cleave('#input', {
blocks: [2, 2, 2, 2],
delimiters: [':', ' 〜 ', ':'],
numericOnly: true,
onValueChanged: function (e) {
let { rawValue } = e.target;
if (result === rawValue) {
return;
}
result = '';
this.properties.blocks.forEach((len, index) => {
if (rawValue.length) {
let sub = rawValue.slice(0, len);
const sub0 = sub.slice(0, 1);
const rest = rawValue.slice(len);
switch (index) {
case 0:
case 2:
if (Number(sub0) > 2) {
sub = `0${sub0}`;
} else if (Number(sub) > 24) {
sub = '24';
}
break;
case 1:
case 3:
if (Number(sub0) > 5) {
sub = `0${sub0}`;
} else if (Number(sub) > 60) {
sub = '00';
}
break;
default:
break;
}
result += sub;
rawValue = rest;
}
});
this.setRawValue(result);
},
});
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ではなく親要素に付けているところがポイント。
スマホやタブレットでタッチ操作をしている場合、 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
でドロップを再現しているけれど、同じ要領で touchmove
で dragover
を再現したりすることが可能。
https://codepen.io/riffrain/pen/yLLWYgq の様に、よくあるドラッグ&ドロップを簡単にするパッケージと組み合わせたり、それの拡張( https://codepen.io/riffrain/pen/yLLWYgq では、本来ドロップできない外の要素でドロップを検知している)をすることができる
オブジェクトをフラットにしたり、フラットなオブジェクトを元に戻したりするJS
lodash の zipObjectDeep
に依存
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;
}