JavaScriptで画面に触れずにページをスクロール！

## きっかけ
このページを読んだのがきっかけでした。  
<a href="https://iphone-mania.jp/news-234027/" target="_blank">【iOS12】iPhoneを傾けるだけ！Webページがスクロールするショートカット</a>

iOS12で実装された「ショートカット」を使って、デバイスの傾きに応じてSafariに表示したWebページをスクロールさせるという内容なんですが、
プログラマーとして、思いました。

__ブックマークレットにした方が便利じゃね？__

ということで、作りました。
ついでにJSのコード短縮のことも書きます。

## 何はともあれ成果物
これをブックマークのURLとして登録して開けば、デバイスを傾けてスクロールできます。

```Javascript:Bookmarklet.js
javascript:let s=innerWidth/10,t,o;document.getElementsByTagName('body')[0].innerHTML+=`<p style='z-index:999;width:${s}px;height:${s}px;position:fixed;right:5%;top:55%;'id='d'>`;addEventListener('deviceorientation',e=>{r=e.beta;o=o||t;if((t-o)**2>9){scrollBy(0,Math.round((.2*(t-o))**3));d.style.backgroundColor='#29F3'}else{d.style.backgroundColor='#29f1'}});d.addEventListener("touchend",e=>{o=t});
```
当たり前ですが、**ジャイロセンサーを搭載しているデバイスじゃないと動きません。**

## そもそもブックマークレットとは

> **ブックマークレット (Bookmarklet)** とは、ユーザーがウェブブラウザのブックマークなどから起動し、ウェブブラウザで簡単な処理を行う簡易的なプログラムのことである。 携帯電話のウェブブラウザで足りない機能を補ったり、ウェブアプリケーションの処理を起動する為に使われることが多い。
> [ブックマークレット - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%96%E3%83%83%E3%82%AF%E3%83%9E%E3%83%BC%E3%82%AF%E3%83%AC%E3%83%83%E3%83%88)

有名なのだと、スマホWeb開発者の必須ツール、Firebug Liteですかね。あれはブックマークレットで動いてます。
iOSのSafariはセキュリティ上の制限で、直接URLバーにJavaScriptを貼り付けて実行することはできないので、ブックマークに登録して使います。

## ショートカット版とブックマークレット版の比較
#### ショートカットに組み込まれていたJS

```Javascript:Tilt-Scroll.js
  const handleOrientation = event => {
  const x = event.beta;

    if (x < 37 && x > 23) return;
    
    if (x > 37 && x <= 45) {
      window.scrollBy(0, 1);
    } else if (x > 45 && x <= 50) {
      window.scrollBy(0, 2);
    } else if (x > 50) {
      window.scrollBy(0, 4);
    } else {
      window.scrollBy(0, -2);
    }
  };

window.addEventListener("deviceorientation", handleOrientation);

completion();
```

条件分岐でスクロールのスピードを変えていますね。
傾きが37〜45のときはスクロールしないようになっています。
ちなみに、最後の`completion()`はショートカットアプリにスクリプトの実行が終了したことを伝えるためのもの。これを呼ばないとエラーになります。

これでもまぁ悪くはないですが、

 - スクロール速度が4通りしかない
 - 個人的には`if〜else`じゃなくて`switch〜case`を使いたい
 - 呼び出し方が面倒↓↓

Safariの共有シートからショートカットアプリを選ぶ
⬇︎
ショートカットが読み込まれるのを待つ
⬇︎
Tilt Scrollショートカットを選択
⬇︎
ショートカットが実行されるのを待つ

とまぁ不便！！
これはなんとかしないといけない(使命感)

#### ブックマークレットのJS

```Javascript:Bookmarklet.js
let size = innerWidth/10; //※
let tilt; //※
let offset; //※

document.getElementsByTagName('body')[0].insertAdjacentHTML( 'afterend' , `<p style='z-index:999;width:${size}px;height:${size}px;position:fixed;right:5%;top:55%; 'id='d'>` );  //※
let dom = document.getElementById('d');  //※

addEventListener('deviceorientation' , event => {
  tilt = event.beta;
  if(offset == undefined){ //※
    offset = tilt;  //※
  }
  if( (tilt-offset)**2 > 9){
    scrollBy( 0 , Math.round(( 0.2*(tilt-offset) )**3) );
    dom.style.backgroundColor='#2299FF33' //※
  }else{
    dom.style.backgroundColor='#2299FF11' //※
  }
});

dom.addEventListener('touchend' , event => { offset = tilt });
```

冒頭のコードを整形して、分かりづらいコードは同等の処理をするコードに置き換えました。
もっと省略できる行には、`//※`と書いておきました。(短縮は後述)

これならブックマークを開くだけなので、使いやすくなりましたね。
更に、いくつか機能を付け足しました。

- 実行時のデバイスの傾きを基準として動作
- オフセットの位置を任意で設定可能
- 自動スクロールが有効かどうかを可視化
- ほぼ無段階のスピード調整

これでかなり実用的になったんじゃないでしょうか。

## さらにコードを短縮
もうここからは完全に自分のこだわりなんですが、せっかくならよりコードを短くしたい！と思ったんです。

```Javascript:変数宣言
let size = innerWidth/10;
let tilt;
let offset;

let s = innerWidth/10,t,o;
//宣言はカンマでまとめられる。
```
<hr>

```Javascript:要素を追加
document.getElementsByTagName('body')[0].insertAdjacentHTML( 'afterend' , `<p style='z-index:999;width:${size}px;height:${size}px;position:fixed;right:5%;top:55%; 'id='d'>` )

document.getElementsByTagName('body')[0].innerHTML+=`<p style='z-index:999;width:${s}px;height:${s}px;position:fixed;right:5%;top:55%;'id='d'>`
```
JavaScriptで要素を追加するときは`insertAdjacentHTML`をよく使うと思いますが、`innerHTML+=`とすることでだいぶ省略できます。ただ、追加する場所を選びたいときには工夫が必要。
<hr>

```Javascript:要素を取得
let d = document.getElementById('d');
```
あまり知られていませんが、実はJavaScriptでは実行時にidと同名の変数が用意されています。なので、この処理無しで`d`を呼んでも何も問題ありません。
<hr>

```Javascript:傾きの初期値を代入
if(offset == undefined){
  offset = tilt;
}

//o=!o?t:o

o=o||t
```
これは、三項演算子です。
`変数 = 条件式 ? trueの時の値 : falseの時の値 ;`とすることで条件分岐による変数への代入を大幅に省略できます。
`!o`となっているのは、真偽値への変換です。`o`の中身が`""`、`0`、`undefined`、`null`のときは`true`、それ以外だと`false`になります。ちなみに、`!!o`とすればこれが逆になります。
つまり、`o=!o?t:o`は`o`に何も入っていなかったら`t`を代入して、そうじゃなかったらそのままという処理になります。(初期値が0の時も問題なし)
`o=!o?t:`としても良いのかもと思って試したんですが、エラーになりました。まぁ、当たり前ですよね。

追記 2019/03/22
この三項演算子について @yuta0801 さんにより短いコードを教えていただきました！
`o=!o?t:o` ➡ `o=o||t` 
<hr>

```Javascript:スピード調整
if (x < 37 && x > 23) return;
if (x > 37 && x <= 45) {
  window.scrollBy(0, 1);
} else if (x > 45 && x <= 50) {
  window.scrollBy(0, 2);
} else if (x > 50) {
  window.scrollBy(0, 4);
} else {
  window.scrollBy(0, -2);
}

if((t-o)**2>9){scrollBy(0,Math.round((.2*(t-o))**3))...
```
![グラフの画像](https://qiita-image-store.s3.amazonaws.com/0/180518/ba314115-e47c-850e-c4db-31775bf92c82.jpeg)
こんなグラフになる3次関数を使って、ストップとスピード調節をまとめました。
さらに、JavaScriptは優しいので`.2`と書けば`0.2`として処理してくれます。
<hr>

###### カラーコード
`rgba(34,153,255,0.51)`
`#2299FF33`
`#29F3`
全部同じ色になります。
<hr>

こうして出来上がったのがこちら。

```Javascript:短縮版
javascript:let s=innerWidth/10,t,o;document.getElementsByTagName('body')[0].innerHTML+=`<p style='z-index:999;width:${s}px;height:${s}px;position:fixed;right:5%;top:55%;'id='d'>`;addEventListener('deviceorientation',e=>{r=e.beta;o=!o?t:o;if((t-o)**2>9){scrollBy(0,Math.round((.2*(t-o))**3));d.style.backgroundColor='#29F3'}else{d.style.backgroundColor='#29f1'}});d.addEventListener("touchend",e=>{o=t});
```

403文字です。ギリギリ原稿用紙に入らないのが悔しい。

削りに削って、ただ単にスクロールするだけなら、こんなに短くなりました。

```Javascript:超短縮版
javascript:addEventListener('deviceorientation',e=>{scrollBy(0,e.beta)});
```

## まとめ
メリークリスマス🎅🎄✨🎁✨