Edited at

タッチモニタもトラックパッドも有るデバイスにおける:hoverの件をDOM属性で解決する


前置き

iPadProやMicrosoftSurfaceではタッチモニタでのクリックも、外部ポインタデバイスでのマウスオーバーも両方あるので、単一のコンポーネントに対して「どっちから使われてもいい感じに見えるように」とか言われたりします。

今回はjs側でクラス制御するのでなく、DOM属性を付与するアプローチで、オールドスクールなCSS書きの人と分業できるアプローチを紹介します


要点


  1. 適当な名前の data-* 属性を対象のコンポーネントのDOMに付与する(例えば data-touch)


  2. ontouchstartdata-touch を常にDOMに付ける


  3. onmouseleavedata-touch を常にDOMから剥がす


  4. :not([data-touch]):hover に対して、これまで通りの素朴なCSSを書く


  5. :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} />);