任意の始点座標・終点座標を結ぶ直線を引きます。
JavaScriptでのグラフィカルな表現はcanvasを使うのが普通かと思いますが、あえてdiv要素を使ってみました。
直線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');
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: 幾何学模様描画
<!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>