前置き
iPadProやMicrosoftSurfaceではタッチモニタでのクリックも、外部ポインタデバイスでのマウスオーバーも両方あるので、単一のコンポーネントに対して「どっちから使われてもいい感じに見えるように」とか言われたりします。
今回はjs側でクラス制御するのでなく、DOM属性を付与するアプローチで、オールドスクールなCSS書きの人と分業できるアプローチを紹介します
要点
- 適当な名前の
data-*
属性を対象のコンポーネントのDOMに付与する(例えばdata-touch
)
-
ontouchstart
でdata-touch
を常にDOMに付ける -
onmouseleave
でdata-touch
を常にDOMから剥がす -
:not([data-touch]):hover
に対して、これまで通りの素朴なCSSを書く -
:not([data-touch]):active, [data-touch]:active
にポインタクリック時やタップされてる時の状態をCSSで書く
以上
Demo
CodePen: Ignore (:hover) style at Touch device
両方のデバイスを受け付けてるSurfaceや、Chromeのモバイルシミュレーションモードでお試しください
ReactHooksを使った簡易なHOCの例
const TouchableContext = React.createContext({
isTouch: false,
handleTouchStart: e => {},
handleMouseLeave: e => {}
});
const touchable = C => {
return props => {
const { onTouchStart, onMouseLeave, ...remain } = props;
const [isTouch, setTouch] = React.useState(false);
const handleTouchStart = e => {
setTouch(true);
if (onTouchStart) {
onTouchStart(e);
}
};
const handleMouseLeave = e => {
setTouch(false);
if (onMouseLeave) {
onMouseLeave(e);
}
};
const init = { isTouch, handleTouchStart, handleMouseLeave };
return (
<TouchableContext.Provider value={init}>
<C
onTouchStart={handleTouchStart}
onMouseLeave={handleMouseLeave}
data-touch={isTouch ? "" : null}
{...remain}
/>
</TouchableContext.Provider>
);
};
};
利用側はほぼ何もしなくてもタッチイベントで data-touch
がDOMに付与されるので、コンポーネントの中身の実装に集中できますし、 useContext
を使って const { isTouch } = useContext(Touchable);
を参照することもできます。
const TextInput = touchable(props => <input type="text" {...props} />);
const Button = touchable(props => <button {...props} />);