19
24

JavaScript マウスをドラッグして描いた円の半径で数値を入力

Last updated at Posted at 2021-08-25

c210828.gif

設置デモ

(スマホには対応していません)

JavaScript

radius_input.js
'use strict';
const radiusInput = (function() {

  const _p = { status: 0 };
  const _el = {};
  const _st = {};

  const defaultOptions = {
    scale: 1,
    decimalplaces: 0,
    offset: 0,
    max: Infinity,
    min: -Infinity,
  };

  window.addEventListener('DOMContentLoaded', function() {
    _el.body = document.querySelector('body');

    // 円表示用要素
    _el.body.insertAdjacentHTML('beforeend', '<div></div>');
    _el.circle = _el.body.lastChild;
    _st.cs = _el.circle.style;
    _st.cs.position = 'fixed';
    _st.cs.boxSizing = 'border-box';
    _st.cs.border = '1px #888 solid';
    _st.cs.backgroundColor = '#fff1';
    _st.cs.borderRadius = '50%';
    _st.cs.zIndex = '9999';
    _st.cs.display = 'none';

    // 始点ポインタ表示用要素
    _el.body.insertAdjacentHTML('beforeend', '<div></div>');
    _el.startPoint = _el.body.lastChild;
    _st.sps = _el.startPoint.style;
    _st.sps.position = 'absolute';
    _st.sps.boxSizing = 'border-box';
    _st.sps.width = _st.sps.height = '8px';
    _st.sps.backgroundColor = '#fff';
    _st.sps.border = '1px solid #000';
    _st.sps.zIndex = '9998';
    _st.sps.display = 'none';

    // 動作時用style マウスポインタ形状変更 他
    _el.body.insertAdjacentHTML('beforeend',
      '<style>*{cursor:crosshair;user-select:none;-webkit-user-select:none;-ms-user-select:none;}</style>');
    _el.cursor = _el.body.lastChild;
    _el.cursor.disabled = true; // 非動作時はdisabled
  });

  // ポインタ移動時
  window.addEventListener('mousemove', function(e) {
    if(_p.status < 2) return;
    // ドラッグ開始点からの半径をもとに円表示及び対象要素へ値を反映
    const r = Math.hypot(_p.x - e.pageX, _p.y - e.pageY);
    _st.cs.width = _st.cs.height = (r * 2) + 'px';
    _st.cs.left = (_p.x - 1 - _el.circle.clientWidth / 2 - window.pageXOffset) + 'px';
    _st.cs.top = (_p.y - 1 - _el.circle.clientHeight / 2 - window.pageYOffset) + 'px';
    _p.target.textContent = _p.target.value =
      (Math.max(Math.min(_p.offset + r * _p.scale, _p.max), _p.min)).toFixed(_p.decimalplaces);
  });

  // ドラッグ開始時
  window.addEventListener('mousedown', function(e) {
    if(_p.status !== 1) return;
    // 右クリック時は動作解除
    if(e.buttons & 2) {
      release();
      addDisableContextmenu();
      return;
    }

    // 始点保存及び始点ポインタ表示
    _st.cs.display = 'block';
    _p.status = 2;
    _p.x = e.pageX;
    _p.y = e.pageY;

    _st.sps.display = 'block';
    _st.sps.left = (_p.x - 1 - _el.startPoint.clientWidth / 2) + 'px';
    _st.sps.top = (_p.y - 1 - _el.startPoint.clientHeight / 2) + 'px';
  });

  // ドラッグ終了時
  window.addEventListener('mouseup', function() {
    removeDisableContextmenu();
    if(_p.status === 0) return;
    release();
  });

  // 動作終了処理
  function release() {
    _st.cs.display = 'none';
    _st.sps.display = 'none';
    _p.status = 0;
    delete _p.target;
    _el.cursor.disabled = true;
  }

  // コンテキストメニュー表示抑制
  function addDisableContextmenu() {
    if(_p.dcm === undefined) {
      window.addEventListener('contextmenu', _p.dcm = function(e) {
        e.preventDefault();
      });
    }
  }

  // コンテキストメニュー表示抑制解除
  function removeDisableContextmenu() {
    setTimeout(function() {
      if(_p.dcm !== undefined) {
        window.removeEventListener('contextmenu', _p.dcm);
        delete _p.dcm;
      }
    }, 100);
  }

  return {
    set: function(element, options) {
      // 対象要素クリック時
      element.addEventListener('click', function() {
        // 初期値設定
        _p.status = 1;
        _st.cs.width = _st.cs.height = 0;
        _st.cs.top = '-10px';
        _p.target = element;
        _el.cursor.disabled = false;

        for(let key in defaultOptions) {
          _p[key] = defaultOptions[key];
        }

        // オプション反映
        if(typeof options === 'object') {
          for(let key in options) {
            const lKey = key.toLowerCase();
            if(defaultOptions[lKey] !== undefined && !isNaN(options[key])) {
              _p[lKey] = + options[key];
            }
          }
          _p.decimalplaces = Math.min(20, Math.max(0, _p.decimalplaces));
        }
      });
    },
  }
}());

使用例

index.html
<!DOCTYPE html>
<html lang='ja'>
  <head>
    <meta charset='utf-8'>
    <title>ドラッグ半径で数値入力</title>
    <script src='radius_input.js'></script>
  </head>
  <body>
    <input type='text' id='test1'><br>
    <input type='text' id='test2'><br>
    <div id='test3'>****</div>

    option指定例<br>
    <input type='text' id='test4'> scale:10<br>
    <input type='text' id='test5'> decimalPlaces:1<br>
    <input type='text' id='test6'> scale:0.1, decimalPlaces:2<br>
    <input type='text' id='test7'> offset:-100, max:0, scale:0.1<br>
    <input type='text' id='test8'> offset: new Date().getFullYear(), min: 1900, scale:-0.1<br>

    <script>
      radiusInput.set(document.getElementById('test1'));
      radiusInput.set(document.getElementById('test2'));
      radiusInput.set(document.getElementById('test3'));

      radiusInput.set(document.getElementById('test4'), {scale: 10});
      radiusInput.set(document.getElementById('test5'), {decimalPlaces: 1});
      radiusInput.set(document.getElementById('test6'), {scale: 0.1, decimalPlaces: 2});
      radiusInput.set(document.getElementById('test7'), {offset: -100, max: 0, scale: 0.1});
      radiusInput.set(document.getElementById('test8'), {offset: new Date().getFullYear(), min: 1900, scale:-0.1});
    </script>
  </body>
</html>

設定方法

radiusInput.set( 対象要素 [,オプション] );

オプションは以下のプロパティをオブジェクトで指定します。
scale ドラッグした長さを値として反映する際の倍率 (デフォルト: 1)
decimalPlaces 小数の桁数 (デフォルト: 0)
offset 開始値のオフセット (デフォルト: 0)
max 最大値 (デフォルト: 上限なし)
min 最小値 (デフォルト: 下限なし)


19
24
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
24