LoginSignup
8
7

canvasを使わずdiv要素で強引に線を引く

Last updated at Posted at 2021-09-02

任意の始点座標・終点座標を結ぶ直線を引きます。

JavaScriptでのグラフィカルな表現はcanvasを使うのが普通かと思いますが、あえてdiv要素を使ってみました。

screenshot.jpg

直線1本ごとに1つの要素を使いますので、大量に線を引く用途には向かないと思います。

line.js
'use strict';
const line = (sx, sy, ex, ey, borderStyle, targetElement) => {
  const target = targetElement && targetElement.nodeName ?
    targetElement : document.querySelector('body');

  const
    e = document.createElement('div'),
    s = e.style;

  target.appendChild(e);
  s.borderTop = borderStyle;

  const
    btw = parseFloat(getComputedStyle(e).borderTopWidth),
    dx = ex - sx,
    dy = ey - sy,
    width = Math.sqrt(dx * dx + dy * dy),
    deg = Math.atan2(dy, dx) * 180 / Math.PI;

  s.position = 'absolute';
  s.width = `${width}px`;
  s.left = `${sx - width / 2}px`;
  s.top = `${sy - btw / 2}px`;
  s.transform = `rotate(${deg}deg) translate(50%)`;

  return e;
};

動作デモ1: 幾何学模様描画

<!DOCTYPE html>
<html lang='ja'>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>line sample</title>
<script src='line.js'></script>
<style>
div.c {
  position: absolute;
  left: 0;
  top: 10px;
  width: 360px;
  height: 360px;
}
</style>
</head>
<body>
<div id='c1' class='c'></div>
<script>
'use strict';
window.addEventListener('DOMContentLoaded', () => {
  let sx, sy, count = 0, h = 0;
  const a = 5 + Math.random() * 15;
  const b = 5 + Math.random() * 15;
  const r1 = 20 + Math.random() * 160;
  const r2 = 180 - r1;
  const target = document.getElementById(`c1`);
  function loop() {
    const ex = 180 + (Math.cos(count / a) * r1 - Math.sin(count / b) * r2);
    const ey = 180 + (Math.sin(count / a) * r1 - Math.cos(count / b) * r2);
    if(sx !== undefined && sy !== undefined) {
      line(sx, sy, ex, ey, `1px hsl(${h}, 100%, 50%) solid`, target);
    }
    h++;
    h %= 360;
    [sx, sy] = [ex, ey];

    if(++count < 1000) requestAnimationFrame(loop);
  }
  loop();
});
</script>
</body>
</html>

動作デモ2: マウスでクリックしたポイント間を直線で繋ぐ

<!DOCTYPE html>
<html lang='ja'>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>line sample2</title>
<script src='line.js'></script>
<style>
* {
  cursor: crosshair;
}
</style>
</head>
<body>
<script>
'use strict';
{
  const isTouchDevice = window.ontouchstart !== undefined;
  const ev = isTouchDevice ? 'touchstart' : 'mousedown';
  let sx, sy;

  window.addEventListener(ev, e => {
    if(e.buttons & 2) {
      document.querySelector('body').textContent = '';
      return;
    }
    const e_ = isTouchDevice ? e.touches[0] : e;
    const ex = e_.pageX;
    const ey = e_.pageY;
    if(sx !== undefined && sy !== undefined) {
      line(sx, sy, ex, ey, '1px brown solid');
    }
    [sx, sy] = [ex, ey];
  });
}
</script>
</body>
</html>

動作デモ3: グラフっぽいもの(値はランダム)

<!DOCTYPE html>
<html lang='ja'>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>line sample</title>
<script src='line.js'></script>
</head>
<body>
<script>
'use strict';
window.addEventListener('DOMContentLoaded', () => draw());

function draw() {
  line(30, 20, 30, 425, '2px black solid').style.zIndex = 9;
  for(let i = 0; i < 4; i++) {
    const x = 30 + (i + 1) * 50;
    line(x, 20, x, 425, '1px #44a dotted');
  }

  let x, x_, y_;
  for(let i = 0; i < 12; i++) {
    const y = 40 + i * 32;
    line(31, y, 31 + Math.random() * 200, y, `10px solid hsl(${i * 20}, 100%, 80%)`);

    x = 120 + Math.random() * 80;
    if(x_ !== undefined) {
      line(x_, y_, x, y, '2px dotted #8af');
    }
    x_ = x;
    y_ = y;
  }
}

</script>
</body>
</html>

動作デモ4: アナログ時計

<!DOCTYPE html>
<html lang='ja'>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>line sample</title>
<script src='line.js'></script>
<style>
* {
  margin: 0;
}
#frame {
  position: relative;
}
#scale, #hands {
  position: absolute;
}
</style>
</head>
<body>
<div id='frame'>
  <div id='scale'></div>
  <div id='hands'></div>
</div>
<script>
'use strict';
const
  p2p60 = (Math.PI * 2) / 60,
  p2p360 = (Math.PI * 2) / 360;

let halfSize, lineWeight, xOffset, yOffset, id;

const drawScale = t => {
  t.textContent = '';
  const
    cx = xOffset + halfSize,
    cy = yOffset + halfSize;

  for (let i = 0; i < 60; i++) {
    const
      f = i % 15 === 0 ? 0 : i % 5 === 0 ? 1 : 2,
      r = p2p60 * i,
      x = Math.cos(r),
      y = Math.sin(r),
      sv = [halfSize * 0.95, halfSize * 0.9, halfSize * 0.85][f];

    line(cx + x * halfSize * 0.8, cy + y * halfSize * 0.8, cx + x * sv, cy + y * sv,
      `${[5, 3, 1][f] * lineWeight}px #ccc ${['double', 'double', 'solid'][f]}`, t);
  }
};

const drawHands = t => {
  t.textContent = '';

  const
    n = (Date.now() / 1000 - new Date().getTimezoneOffset() * 60) % 86400,
    hour = (n / 120) % 360 - 90,
    minute = (n / 10) % 360 - 90,
    second = (Math.floor(n) * 6) % 360 - 90,
    cx = xOffset + halfSize,
    cy = yOffset + halfSize;

  const
    hr = p2p360 * hour,
    hl = halfSize * 0.5;

  line(cx, cy, cx + Math.cos(hr) * hl, cy + Math.sin(hr) * hl,
    `${9 * lineWeight}px #555 solid`, t);

  const
    mr = p2p360 * minute,
    ml = halfSize * 0.8;

  line(cx, cy, cx + Math.cos(mr) * ml, cy + Math.sin(mr) * ml,
    `${5 * lineWeight}px #555 solid`, t);

  const
    sr = p2p360 * second,
    sl = halfSize * 0.82;

  line(cx, cy, cx + Math.cos(sr) * sl, cy + Math.sin(sr) * sl,
    `${3 * lineWeight}px #884 solid`, t);

  const
    sr2 = p2p360 * (second + 180),
    sl2 = halfSize * 0.25;

  line(cx, cy, cx + Math.cos(sr2) * sl2, cy + Math.sin(sr2) * sl2,
    `${6 * lineWeight}px #884 solid`, t);

  id = setTimeout(drawHands, 1000 - Date.now() % 1000, t);
};

const drawClock = () => {
  halfSize = Math.min(parseFloat(innerHeight), parseFloat(innerWidth)) / 2;
  lineWeight = halfSize * 0.005;
  xOffset = (parseFloat(innerWidth) / 2 - halfSize);
  yOffset = (parseFloat(innerHeight) / 2 - halfSize);
  drawScale(document.getElementById('scale'));
  drawHands(document.getElementById('hands'));
};

addEventListener('DOMContentLoaded', drawClock);
addEventListener('resize', () => {
  clearTimeout(id);
  drawClock();
});
</script>
</body>
</html>
8
7
0

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
8
7