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

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

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

![screenshot.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618708/4f31ab22-a7c0-2c76-370a-837a36125802.jpeg)

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

```js: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');
  target.appendChild(e);
  e.style.borderTop = borderStyle;

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

  Object.assign(e.style, {
    position : 'absolute',
    width    : `${width}px`,
    left     : `${sx - width / 2}px`,
    top      : `${sy - btw / 2}px`,
    transform: `rotate(${deg}deg) translate(50%)`,
  });

  return e;
};
```

----

#### [動作デモ1: 幾何学模様描画](https://akebi.jp/temp/line/index.html) ####

```html
<!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: マウスでクリックしたポイント間を直線で繋ぐ](https://akebi.jp/temp/line/index2.html) ####

```html:
<!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: グラフっぽいもの(値はランダム)](https://akebi.jp/temp/line/index3.html) ####

```html:
<!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: アナログ時計](https://akebi.jp/temp/line/index4.html) ####

```html:
<!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>
```