问题描述
假设我某个页面上使用了<svg>
,其中包括一个<circle>
。我希望实现的是:在circle上点击某个位置,该位置出现一个闪烁的光标。
解决思路
要实现这个效果,需要考虑3个问题:
- 如何实现光标?
- 如何实现闪烁效果?
- 如何将光标移动到鼠标点击的位置?
对于第1个问题,最直观的想法就是用<line>
实现,然后通过修改x1
、y1
、x2
和y2
的数值(即线段两个端点的坐标)实现光标的移动。
对于第2个问题,可以使用 jQuery 的fadeOut
和fadeIn
方法来实现元素的淡出和淡入效果,并且通过setInterval
方法,在周期内重复淡出和淡入过程,从而实现持续的闪烁效果。
对于第3个问题,重点在于,计算鼠标点击位置e与圆心c的连线L,与通过c的垂线,顺时针方向的夹角,通过这个夹角,可以计算出line两个端点的坐标。需要注意的是,我的思路是将<line>
放在<svg>
标签下,所以计算夹角时,用的是窗口坐标系中的e、c和半径r,并非svg坐标系下的对象;但是计算line端点坐标时,使用的是svg坐标系下对应值。
3.1 计算L与圆的交点
首先需要考虑L是水平线和垂直线的情况,因为联立计算时,可能出现除0错误。假设圆心
C
C
C坐标为
(
x
0
,
y
0
)
(x_0, y_0)
(x0,y0),半径为
r
r
r,圆外某点
P
P
P坐标为
(
x
1
,
y
1
)
(x_1, y_1)
(x1,y1),要计算直线
P
C
PC
PC与圆的交点,可以这样计算:
直线
P
C
的斜率为
:
k
=
y
1
−
y
0
x
1
−
x
0
直线PC的斜率为: k = \frac{y_1 - y_0}{x_1 - x_0}
直线PC的斜率为:k=x1−x0y1−y0
P
C
的方程为:
y
=
m
(
x
−
x
0
)
+
y
0
PC的方程为:y = m(x - x_0) + y_0
PC的方程为:y=m(x−x0)+y0
圆的方程为:
(
x
−
x
0
)
2
+
(
y
−
y
0
)
2
=
r
2
圆的方程为:(x - x_0)^2 + (y - y_0)^2 = r^2
圆的方程为:(x−x0)2+(y−y0)2=r2
将PC的方程代入圆的方程,整理得:
(
1
+
m
2
)
x
2
−
2
x
0
(
1
+
m
2
)
x
+
(
1
+
m
2
)
x
0
2
−
r
2
=
0
(1+m^2)x^2-2x_0(1+m^2)x+(1+m^2)x_0^2-r^2=0
(1+m2)x2−2x0(1+m2)x+(1+m2)x02−r2=0
这样相当于已知一元二次方程的a、b和c了,所以可以直接使用公式求解:
x
=
−
b
±
b
2
−
4
a
c
2
a
x=\frac{-b\pm\sqrt{b^2-4ac}}{2a}
x=2a−b±b2−4ac。
此时可以根据
P
P
P点在
C
C
C点的左侧还是右侧判断选哪个根。
注意:窗口坐标系是向右为x增长方向,向下为y增长方向。
3.2 计算夹角
这里不展开了,主要是利用arcsin
。js里方法是Math.asin()
,计算结果是弧度。
3.3 已知夹角计算圆上某点的坐标
如下图所示(通过设置不同的r值可以计算两个端点的坐标):
P
x
=
C
x
+
r
∗
s
i
n
(
θ
)
P_x = C_x + r * sin(θ)
Px=Cx+r∗sin(θ)
P
y
=
C
y
−
r
∗
c
o
s
(
θ
)
P_y = C_y - r * cos(θ)
Py=Cy−r∗cos(θ)
其中js里用的方法是Math.sin()
和Math.cos()
,参数都是弧度,需要注意转换。
代码结构
注意:已去除实现细节。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div>
<svg width="100%" height="1000" xmlns="http://www.w3.org/2000/svg">
<circle id="myCircle1" cx="600" cy="480" r="285" fill="#fff" stroke="#ccc" stroke-width="1" />
<!-- 先隐藏line -->
<line x1="1" y1="11" x2="2" y2="2" id="cursor" stroke="#1F2744" stroke-width="1" style="display:none;"></line>
</svg>
</div>
<script>
// 设置闪烁
function startBlinkingJQuery(element, interval) {
setInterval(() => {
$(element).fadeOut(interval / 2).fadeIn(interval / 2);
}, interval);
}
// 获取光标
var cursor = document.getElementById("cursor");
const intervalJQuery = 900; // 设置闪烁间隔,单位是毫秒
// 获取svg和circle
const svg = document.querySelector('svg');
const circle = document.getElementById('myCircle1');
// 添加监听
circle.addEventListener('mousedown', (e) => {
const bbox = circle.getBoundingClientRect();
const centerX = bbox.x + bbox.width / 2;
const centerY = bbox.y + bbox.height / 2;
const radius = bbox.width / 2;
const clickX = e.clientX;
const clickY = e.clientY;
// 使用窗口中的圆心和点击点计算夹角,省略函数的实现
let angle = calculateCircleInfo(centerX, centerY, radius, clickX, clickY);
// 根据夹角计算svg上两个端点的坐标,返回的是数组。省略函数的实现
let result = calSvgLine(angle);
//console.log(result);
cursor.setAttribute('x1', result[0]);
// 依次修改x1, y1, x2, y2, 此处省略
// 设置闪烁
startBlinkingJQuery(cursor, intervalJQuery);
});
</script>
</body>
</html>