iOS9で追加されたForceTouch(3DTouch)をJavaScriptで操作してみる
先週、2年使って画面のバッキバキになったiPhone 5s
を6s
に買い換えました。
買い換えてみて便利だなぁと思ったのは、やっぱりForce Touch
(3D Touch
)でした!
カーソルの移動や、ブラウザでリンク先をプレビュー出来たりなどなど。
そんな便利なForch Touch
をJavaScript
から操作する方法はあるのかな?とちょっと調べたら既にやっている方がいました。
こちらを参考にして、操作をしてみたいと思います。
ちなみに2番目の記事にも書かれていたのですが、はじめAppleのドキュメントを見ながらサンプルを作ってみたのですが、期待するイベントがうんともすんとも言いませんでした。
ここらへんの対応は今後のバージョンアップで、ということなんでしょうかね。
タッチの強度を取得する
タッチイベントのイベントオブジェクト内に、force
という値が設定されています。
この中に、0~1
までで表されたのタッチの強さが入っています。
具体的には下記のようなコードで取得できます。
document.getElementById("btn").addEventListener("touchstart", function(e){
console.log(e.touches[0].force); //0 ~ 1
}, false);
思いの外簡単な操作で取得できるようですね!
とってもシンプルなサンプルを作る
タッチの強さを取得できたので、シンプルなサンプルを作ってみました。
画面中央の円に対するタッチの強度で、影と背景のコントラストに変化が出るシンプルなものです。
※PCでは動かないので、是非実機でご確認下さい…
サンプルコード
上記のサンプルで使用しているコードはこんな感じです。
HTML
/CSS
/JS
全部一緒に書いてしまったので、少し見づらいですがコピペで動かせるようになってます。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<style type="text/css">
* {
margin:0;
padding:0;
box-sizing:border-box;
}
body {
overflow:hidden;
font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;
}
h1 {
position:absolute;
top:30px;
left:0;
z-index:1;
width:100%;
color:#fff;
font-size:28px;
font-weight:lighter;
text-align:center;
letter-spacing:.05em;
}
#btn {
position:absolute;
top:0;
right:0;
bottom:0;
left:0;
z-index:1;
width:150px;
height:150px;
margin:auto;
background:#fff;
border-radius:100%;
color:#607d8b;
text-align:center;
font-weight:lighter;
line-height:150px;
letter-spacing:.05em;
}
#value {
position:absolute;
bottom:30px;
left:0;
z-index:1;
width:100%;
color:#607d8b;
text-align:center;
font-weight:lighter;
}
#background {
position:absolute;
top:0;
left:0;
z-index:0;
width:100%;
height:100%;
background:linear-gradient(to bottom, #d69af4 0%,#faffcc 100%);
}
</style>
</head>
<body>
<h1>Force Touch Sample</h1>
<div id="btn">Touch me</div>
<div id="value"></div>
<div id="background"></div>
<script>
var force = 0,
touch = null;
var $btn = document.getElementById("btn"),
$value = document.getElementById("value"),
$background = document.getElementById("background");
// イベントをキャンセル
function cancelEvent(e){
e.preventDefault();
e.stopPropagation();
}
// タッチを取得して、値の更新を実行する
function checkForce(e){
cancelEvent(e);
touch = e.touches[0];
refreshForceValue();
}
// タップの強さを更新する
function refreshForceValue(){
if( !touch ) return;
force = touch.force;
render();
setTimeout(refreshForceValue, 10);
}
// タップの強さを視覚的にフィードバック
function render(){
$value.innerHTML = "Force : " + force;
$btn.style.boxShadow = "0 0 " + ( 10 * force ) + "px rgba(0, 0, 0, " + ( 0.8 * force ) + ")";
$background.style.webkitFilter = "contrast(" + ( 100 - 40 * force ) + "%)";
}
// イベントを設定
$btn.addEventListener("touchstart", checkForce, false);
$btn.addEventListener("touchmove", checkForce, false);
$btn.addEventListener("touchend", function(e){
cancelEvent(e);
touch = null;
force = 0;
render();
}, false);
// 初期化
render();
</script>
</body>
</html>
実際のWebアプリにどう活かすか
じゃあこれをどう活かしていくか、っていうところが悩ましい気がします。対応する端末も限られますし。
Force Touch
を前提とした機能にするのは微妙な気がするので、PC
でいうところの右クリック的な扱いにならざるを得ない気がします。(対応端末が上がってきたら別かもですね!)
サンプルを作ってみた
Force Touch
に補助的な操作を任せる、具体的にどんなものがあるかなぁ〜と悩んだので、TODO
アプリを想定したサンプルを作ってみました。
動作は下記のような感じです。
- 各
TODO
のタップでチェック/チェック解除 - テキスト部分のタップで内容を編集
- 各
TODO
のForce Touch
で次の操作を表示- 編集
- チェック
- 削除
イベント操作を雑にクラス化
冒頭にも書いたように、Apple
のドキュメントに記載された下記のイベントが発行されませんでした。
webkitmouseforcewillbegin
webkitmouseforcedown
webkitmouseforceup
webkitmouseforcechanged
Force Touch
の開始と終了、一定の強度まで達したかどうかなど、結構重要なイベントですね…。
これらが使えないのは地味に不便なので、サンプルでは下記の様にイベントをラップするようなクラスを作っています。
import {EventEmitter} from "events"
import {cancelEvent} from "../utils/events"
class ForceTouchEvent extends EventEmitter {
constructor($el, threshold=0.95) {
super();
this.$el = $el;
this.threshold = threshold;
this.force = 0;
this.touch = null;
this.interval = 10;
this.down = false;
this.bindEvents();
}
bindEvents() {
this.$el.addEventListener("touchstart", this.handleTouchStart.bind(this), false);
this.$el.addEventListener("touchmove", this.handleTouchMove.bind(this), false);
this.$el.addEventListener("touchend", this.handleTouchEnd.bind(this), false);
}
unbindEvents() {
this.$el.removeEventListener("touchstart", this.handleTouchStart.bind(this), false);
this.$el.removeEventListener("touchmove", this.handleTouchMove.bind(this), false);
this.$el.removeEventListener("touchend", this.handleTouchEnd.bind(this), false);
}
handleTouchStart(e) {
this.trigger(ForceTouchEvent.WILL_BEGIN);
this.checkForce(e);
}
handleTouchMove(e) {
this.checkForce(e);
}
handleTouchEnd(e) {
if( !this.down && this.force < this.threshold ){
this.trigger(ForceTouchEvent.UP);
}
this.touch = null;
this.down = false;
}
checkForce(e) {
if( !e.touches ) return;
this.touch = e.touches[0];
this.refreshForceValue();
}
refreshForceValue() {
if( !this.touch ) return;
let force = this.touch.force;
// change
if( this.force != force ){
this.force = force;
this.trigger(ForceTouchEvent.CHANGE);
}
// down
if( !this.down && this.force >= this.threshold ){
this.down = true;
this.trigger(ForceTouchEvent.DOWN);
return;
}
setTimeout(this.refreshForceValue.bind(this), this.interval);
}
trigger(type, ...args) {
let e = {};
e.type = type;
e.timestamp = Math.floor(Date.now() / 1000);
e.target = this.$el;
e.force = this.force;
this.emit(type, e, args);
}
}
ForceTouchEvent.WILL_BEGIN = "forcetouchwillbegin";
ForceTouchEvent.CHANGE = "forcetouchchange";
ForceTouchEvent.DOWN = "forcetouchdown";
ForceTouchEvent.UP = "forcetouchcup";
export default ForceTouchEvent;
EventEmitter
を継承して、Force Touch
の始まり/終わり、値の変化を通知するような内容です。
これを次のように使うことで、発行されないイベントを補ってみました。
import ForceTouchEvent from "../events/ForceTouchEvent"
let $el = document.getElementById("hoge");
let forceTouch = new ForceTouchEvent($el);
// Force Touch開始
forceTouch.on(ForceTouchEvent.WILL_BEGIN, e => {
console.log(e.force); //0 ~ 1
});
// 強度が変更された
forceTouch.on(ForceTouchEvent.CHANGE, e => {
console.log(e.force); //0 ~ 1
});
// 規程の強度に達した
forceTouch.on(ForceTouchEvent.DOWN, e => {
// logic
});
// 規程の強度に達しないで、指が離れた
forceTouch.on(ForceTouchEvent.UP, e => {
// logic
});
サンプルコード全体
はじめ、全体のコードを載せようと思っていたのですが、思っていたよりも長くなってしまったので興味のある方は下記リポジトリよりご確認ください。(雑なコードで見づらいかもしれないです…)
まとめ
ドキュメント通りのイベントが発行されないのは痛いですが、値の取得自体は簡単なので上手く使えば面白い表現や、UI
に活かせそうだなと思います。
実際にサンプルを作ってみて、Force Touch
を視覚的にフィードバックを返すのは簡単ですが、それが可能だよという事を分かってもらえる「デザイン」が難しそうだなという気がしました。
これから対応アプリや、Web
サイトが増えていくのが楽しみですね!
Comments