アンケートサイトで使える!ドラッグ・アンド・ドロップで選択した範囲を一括でクリックするブックマークレット

ドラッグ・アンド・ドロップで選択した範囲を一括でクリックするブックマークレットを作った。

アンケートサイトなんかで、大量のラジオボタンやチェックボックスをクリックするのが面倒になった時は、コチラをドウゾ。

サンプル

以下のサンプルをドウゾ。

画面内をドラッグすると水色の選択領域が見えるので、そのままドラッグしてチェックボックスをいくつか囲んでみよう。ドラッグ・アンド・ドロップを終了した領域にある要素に対して click() イベントを発火させるので、チェックボックスが一括でセットできるだろう。

ブックマークレット用コード

ブックマークレットに使えるコードは以下。

javascript:((e,t,o,a,l,n,p,s,i,d,r,h,c,g,m,u)=>{r=(d=(e=>(e=t.createElement("i"),t.body.appendChild(e),e)))(),h=d(),t[o]("mousedown",t=>{p=!0,r[l].cssText=i+"border:1px solid rgba(0,0,255,.2);background:rgba(99,255,255,.2)",h[l].cssText=i,c=t.pageX,g=t.pageY,m=e.scrollX,u=e.scrollY}),t[o]("mousemove",(e,t,o)=>{p&&(s=!0,t=e.pageX-c,r[l].left=(t<0?c+t:c)+n,o=e.pageY-g,r[l].top=(o<0?g+o:g)+n,r[l].width=Math.abs(t)+n,r[l].height=Math.abs(o)+n)}),t[o]("mouseup",(o,i,d,r,b,f,x,M)=>{if(p=!1,s)for(s=!1,i=o.pageX,d=o.pageY,r=[],f=Math.min(g,d);f<Math.max(g,d);f+=9)for(e.scrollTo(m,u),b=Math.min(c,i);b<Math.max(c,i);b+=9)h[l].top=f+n,h[l].left=b+n,x=h[a](),e.scrollTo(x.left,x.top),x=h[a](),(M=t.elementFromPoint(x.left,x.top))&&!r.includes(M)&&(M.click(),r.push(M))})})(window,document,"addEventListener","getBoundingClientRect","style","px",!1,!1,"position:absolute;top:0;left:0;width:1px;height:1px;pointer-events:none;");

ソースコード

ソースコードは以下。

((win, doc, addEvent, getRect, sty, px, isMouseDown, isDragging, defaultStyle, createElement, rangeElem, pointElem, beginX, beginY, beginScrollX, beginScrollY) => {
  // 要素を生成して返す関数を作る
  createElement = (elem) => {
    elem = doc.createElement('i');
    doc.body.appendChild(elem);
    return elem;
  };
  // 選択範囲を示す要素
  rangeElem = createElement();
  // ポインタ要素
  pointElem = createElement();
  
  // マウスボタン押下
  doc[addEvent]('mousedown', (event) => {
    // マウスボタン押下中
    isMouseDown = true;
    // 選択範囲を消すため初期値を再指定する
    rangeElem[sty].cssText = defaultStyle + 'border:1px solid rgba(0,0,255,.2);background:rgba(99,255,255,.2)';
    pointElem[sty].cssText = defaultStyle;
    // 選択開始位置を控えておく
    beginX = event.pageX;
    beginY = event.pageY;
    // 選択開始時のスクロール位置を控えておく
    beginScrollX = win.scrollX;
    beginScrollY = win.scrollY;
  });
  
  // マウス移動中
  doc[addEvent]('mousemove', (event, x, y) => {
    // マウスボタン押下中の移動なら処理する
    if(isMouseDown) {
      // ドラッグ中
      isDragging = true;
      // 始点より左に移動する場合を考慮して left を制御する
      x = event.pageX - beginX;
      rangeElem[sty].left = (x < 0 ? beginX + x : beginX) + px;
      // 始点より上に移動する場合を考慮して top を制御する
      y = event.pageY - beginY;
      rangeElem[sty].top = (y < 0 ? beginY + y : beginY) + px;
      // 始点と現在の位置の差を絶対値にして幅・高さとして設定する
      rangeElem[sty].width  = Math.abs(x) + px;
      rangeElem[sty].height = Math.abs(y) + px;
    }
  });
  
  // マウスボタンを離した時
  doc[addEvent]('mouseup', (event, endX, endY, clickedElems, x, y, position, elem) => {
    // マウスボタンを離した
    isMouseDown = false;
    // ドラッグ中なら処理する
    if(isDragging) {
      // ドラッグ終了
      isDragging = false;
      // 終了位置を控えておく
      endX = event.pageX;
      endY = event.pageY;
      // クリックした要素を入れておく配列
      clickedElems = [];
      // 左上から 9px ずつズラす (圧縮時に1桁の数値で済ませたいので…)
      // elementFrompoint はスクロールして画面上に要素が見えていないと null になってしまうので、スクロールして対象要素が画面内に表示されている状態にしている
      for(  y = Math.min(beginY, endY); y < Math.max(beginY, endY); y += 9) {
        // 開始位置にスクロールし直す
        win.scrollTo(beginScrollX, beginScrollY);
        for(x = Math.min(beginX, endX); x < Math.max(beginX, endX); x += 9) {
          pointElem[sty].top  = y + px;
          pointElem[sty].left = x + px;
          // 絶対配置したポインタ要素の位置にスクロールする
          position = pointElem[getRect]();
          win.scrollTo(position.left, position.top);
          // ポインタ要素の座標を再度拾い、その位置にある要素を取得する
          position = pointElem[getRect]();
          elem = doc.elementFromPoint(position.left, position.top);
          // クリック済の要素は無視してクリックする
          if(elem && !clickedElems.includes(elem)) {
            elem.click();
            clickedElems.push(elem);
          }
        }
      }
    }
  });
})(
  window,                   // win
  document,                 // doc
  'addEventListener',       // addEvent
  'getBoundingClientRect',  // getRect
  'style',                  // sty
  'px',                     // px
  false,                    // isMouseDown
  false,                    // isDragging
  'position:absolute;top:0;left:0;width:1px;height:1px;pointer-events:none;'  // defaultStyle
  // createElem
  // rangeElem
  // pointElem
  // beginX
  // beginY
  // beginScrollX
  // beginScrollY
);

「ブラウザで見えている範囲の要素でないとうまく操作できない」というところが面倒臭かった。

スクロールを伴うドラッグ・アンド・ドロップをサポートしないのであれば、スクロールし直したりする必要もなく、rangeElemtopleft の座標から widthheight の値まで elementFromPoint() を繰り返せばいいだけ。もう少し楽に実装できるのだが…。

とりあえずコレで、大量に領域を選択して、一気にクリックできるようになったので、雑にアンケートサイトの回答ができるようになった。w


以下続編。