likes
comments
collection
share

两点一线:CSS + 原生 JS 连接两个 DIV 元素

作者站长头像
站长
· 阅读数 60

1 问题引出

项目上遇到这样一个需求:给文本块加批注,然后将批注块与文本块用连接线连接起来,效果如图:

两点一线:CSS + 原生 JS 连接两个 DIV 元素

本文以原生 JS 的环境,来对连接线做实现与讲解。

2 实现思路及过程

2.1 斜线的样式实现

“连接线” ,先抛开 “连接” 不谈,首先它是一条 “线” ,我们最先考虑如何在页面的任意位置画一条斜线:

<div class='line'></div>

.line {
  position: absolute; 
  background-color: #333333;
  width: 200px;
  height: 1px;
  top: 300px;
  left: 400px;
  transform: rotate(0.5rad);
}
  • 选取 div 元素, width 设置线的长度, height 设置线的粗细;

  • 通过 position: absolutetopleft 属性,来设置线在页面中的位置;

  • 通过 transform: rotate() 来将线旋转某一弧度(rad)或角度(deg)。

2.2 问题分解

连接两个 div ,其实是在每个 div 中选取一个点,将所选取的两个点进行连接。

结合上面斜线的样式实现方案,我们可以将连接两个 div 的问题进一步分解为:

(1)从两个 div 中分别选取一个点作为连接点,并计算出两个连接点的 坐标

(2)通过两个点的坐标,计算出斜线样式所需的 widthtoplefttransform: rotate 的数值,也就是连接线的 长度位置旋转弧度

(3)动态创建连接线 DOM ,设置其样式,添加到页面中。

2.3 连接点的坐标计算

从 div 中选取连接点,通常可以选取 div 元素块的中心点、或 某一条边框的中心点、或 某一个顶点,根据自身的审美选择即可。

通过 div 元素在浏览器可见区域的 top 、 left 值,以及其自身宽高,可以计算出两个点在浏览器可见区域中的坐标,如计算元素块的中心点坐标:

var obj = document.getElementById('divID')
var x = obj.getBoundingClientRect().left + obj.clientWidth / 2
var y = obj.getBoundingClientRect().top + obj.clientHeight / 2

2.4 连接线的长度计算

有了两个连接点的坐标后,我们可以通过勾股定理,计算出连接线的长度:

var length = Math.sqrt((x2 -x1) * (x2 -x1) + (y2 -y1) * (y2 -y1))

两点一线:CSS + 原生 JS 连接两个 DIV 元素

注:图中的坐标系并非数学意义上严格的直角坐标系,而是按照网页样式布局的习惯所画的示意图,其 y 轴负方向(向下方向)为正值。

2.5 连接线的旋转弧度计算

Math.atan2(y, x) 方法可以计算二维坐标系中任意一个点 (x, y) 和 原点 (0, 0) 的连线与 x 轴正半轴的夹角大小(弧度值)。

将连接线平移,使其原 (x1, y1) 点与远点 (0, 0) 重合,则原 (x2, y2) 点移动至 (x2 -x1, y2 -y1) ,套用 Math.atan2(y, x) 即可计算出连接线的旋转弧度:

var rad = Math.atan2((y2 -y1), (x2 -x1))

两点一线:CSS + 原生 JS 连接两个 DIV 元素

2.6 连接线的位置计算

连接线的位置,指的是连接线在未做旋转前(旋转是按照元素的中心点进行旋转),其左上顶点的坐标,计算如下:

var top = (y1 + y2) / 2
// length = Math.sqrt((x2 -x1) * (x2 -x1) + (y2 -y1) * (y2 -y1))
var left = (x1 + x2) / 2 - length / 2

两点一线:CSS + 原生 JS 连接两个 DIV 元素

2.7 创建连接线 DOM

斜线样式所需的所有属性值都计算完毕,最后创建 DOM ,为其设置行内样式,再将 DOM 添加到页面中即可:

var line = document.createElement('div')
var style = 'position: absolute; background-color: #333333; height: 1px; top:' + top +
            'px; left:' + left + 'px; width: ' + length + 'px; transform: rotate(' + rad + 'rad);'
line.setAttribute('style', style)
document.body.appendChild(line)

3 完整代码样例

最后给出完整的样例代码,连接了 5 个圆形的中心点绘制出一个五角星:

两点一线:CSS + 原生 JS 连接两个 DIV 元素

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
  </head>
  <body>
    <div id="e1" class="element element_1"></div>
    <div id="e2" class="element element_2"></div>
    <div id="e3" class="element element_3"></div>
    <div id="e4" class="element element_4"></div>
    <div id="e5" class="element element_5"></div>
  </body>
</html>
<style type="text/css">
  .element { position: absolute; width: 50px; height: 50px; border-radius: 25px; background-color: yellow; }
  .element_1 { top: 100px; left: 175px; }
  .element_2 { top: 175px; left: 75px; }
  .element_3 { top: 175px; left: 275px; }
  .element_4 { top: 300px; left: 100px; }
  .element_5 { top: 300px; left: 250px; }
</style>
<script type="text/javascript">
  function drawLine (obj1, obj2) {
    // 起点坐标
    var x1 = obj1.getBoundingClientRect().left + obj1.clientWidth / 2
    var y1 = obj1.getBoundingClientRect().top + obj1.clientHeight / 2

    // 终点坐标
    var x2 = obj2.getBoundingClientRect().left + obj2.clientWidth / 2
    var y2 = obj2.getBoundingClientRect().top + obj2.clientHeight / 2

    // 计算连接线长度
    var length = Math.sqrt((x2 -x1) * (x2 -x1) + (y2 -y1) * (y2 -y1))

    // 计算连接线旋转弧度值
    var rad = Math.atan2((y2 -y1), (x2 -x1))

    // 连接线未旋转前,起点坐标计算
    var top = (y1 + y2) / 2
    var left = (x1 + x2) / 2 - length / 2

    // 创建连接线 dom 节点,并设置样式
    var line = document.createElement('div')
    var style = 'position: absolute; background-color: red; height: 1px; top:' +
                top + 'px; left:' + left + 'px; width: ' + length + 'px; transform: rotate(' + rad + 'rad);'
    line.setAttribute('style', style)
    document.body.appendChild(line)
  }

  drawLine(document.getElementById('e1'), document.getElementById('e4'))
  drawLine(document.getElementById('e1'), document.getElementById('e5'))
  drawLine(document.getElementById('e2'), document.getElementById('e3'))
  drawLine(document.getElementById('e2'), document.getElementById('e5'))
  drawLine(document.getElementById('e3'), document.getElementById('e4'))
</script>