什么!你要让我在两点之间画条线?

功能说明

产品要做一个类似产品对比的功能,但是两者之间并不完全相似,只是某些字段需要进行比对,为了更明显的展示对比的两项,产品要求两者之间画出一条线来表示两者可以进行对比。

我在进行功能拆分之后,我发现本质上其实就是两个点之间的连线,大概实现后效果是这样子的

实现方法

当然可能还有更好的方法,会有更高的性能,此瑾代表为大家提供一个思路而已,同时代码本质只是一个demo,并未进行优化(懒)

我先来说本质,画线的本质其实就是渲染两个div盒子 ,根据要画线的两点之间的位置,计算出两个盒子的高度和宽度,然后分别按照业务情况去渲染他们的border ,再根据两点的offsetTop和offsetLeft 计算出两个盒子分别的top和left ,绝对定位到大盒子中,最后再往渲染的两点画出一个圆圈就大功完成了

在设计这个功能时,应编写出一个组件,只暴露出要渲染两个点的offsetTop和offsetLeft ,其余在内部实现,让功能保持单一原则,最小暴露原则,高内聚

下面我将用原生Js和React的形式来为大家展示代码将如何进行编写

原生Js

首先我们先捋清楚实现这个效果需要几个步骤,可能整个功能会很难,感觉没有思路,但是可以一点点的拆分功能,最后实现这个功能,这个也是我在工作中的开发思路

通过本质,我们可以知道我们需要一个绝对定位盒子的高度和宽度,通过设置它的top和left,来达到视觉上的画线,所以要编写一个专门用来画线的函数,参数为高度,宽度,top,left和画线的type,最后再插入到父元素中

js 复制代码
// 仅供参考,可优化
function createLineElement({ width, height, type, left, top }) {
	const line = document.createElement('div');
	line.style.width = width + 'px';
	line.style.height = height + 'px';
	line.style.position = 'absolute';
	line.style.left = left + 'px';
	line.style.top = top + 'px';
	if (type === 1) {
		line.style.borderBottom = '2px dashed #ff5b4d';
		line.style.borderRight = '2px dashed #ff5b4d';
	} else if (type === 2) {
                line.style.borderTop = '2px dashed #ff5b4d';
		line.style.borderLeft = '2px dashed #ff5b4d';
	} else if (type === 3) {
		line.style.borderTop = '2px dashed #ff5b4d';
	} else if (type === 4) {
		line.style.borderTop = '2px dashed #ff5b4d';
		line.style.borderRight = '2px dashed #ff5b4d';
	} else if (type === 5) {
		line.style.borderBottom = '2px dashed #ff5b4d';
		line.style.borderLeft = '2px dashed #ff5b4d';
	}
		fatherEL.appendChild(line);
	}

但是盒子的高度我们是通过两点的offsetTop和offsetLeft来动态的计算出来的 ,我们需要考虑当两个点的高度,哪边高,哪边低,来进行动态的渲染 ,其次大家也可在这里来编写业务中需要几种类型的画线方式 ,是主要的逻辑处理区

js 复制代码
//仅供参考,可优化
function renderElement({ leftEl, rightEl, type }) {
	const { LeftScroll, LeftScrollTop } = leftEl;
	const { rightScroll, rightScrollTop } = rightEl;

        const middleWidth = boxEl.clientWidth / 2;
	const styleEl1 = {
                height: 0,
		width: 0,
		type: 1,
		left: LeftScroll,
		top: 0,
	};
	const styleEl2 = {
	height: 0,
	width: 0,
	type: 3,
	left: middleWidth,
	top: 0,
	};

	if (type === 1) {
	if (rightScrollTop > LeftScrollTop) {
	styleEl1.height = rightScrollTop - LeftScrollTop;
	styleEl2.height = rightScrollTop - LeftScrollTop;
	styleEl1.top = LeftScrollTop;
	styleEl2.top = LeftScrollTop + styleEl2.height;
	styleEl1.type = 4;
	styleEl2.type = 3;
	} else {
	styleEl1.height = LeftScrollTop - rightScrollTop;
	styleEl2.height = LeftScrollTop - rightScrollTop;
	styleEl1.top = LeftScrollTop - styleEl1.height;
	styleEl2.top = LeftScrollTop - styleEl2.height;
	}
	styleEl1.width = middleWidth - LeftScroll;
	styleEl2.width = rightScroll - middleWidth;
	createCircleElement(LeftScroll, LeftScrollTop, '>');
	createCircleElement(rightScroll, rightScrollTop, '>');
	} else if (type === 2) {
	styleEl1.height = 22;
	styleEl1.width = middleWidth - LeftScroll;
	styleEl2.width = rightScroll - middleWidth + 5;
	styleEl1.top = LeftScrollTop;
	styleEl1.type = 5;

	if (rightScrollTop > LeftScrollTop) {
	styleEl2.height = rightScrollTop - LeftScrollTop - 22;
	styleEl2.width = rightScroll - middleWidth + 5;
	styleEl2.type = 5;
	styleEl2.top = LeftScrollTop + 22;
	createCircleElement(LeftScroll, LeftScrollTop, '^');
	createCircleElement(rightScroll, rightScrollTop, '>');
	} else {
	styleEl2.height = LeftScrollTop - rightScrollTop + 22;
	styleEl2.type = 2;
	styleEl2.top = rightScrollTop;
	createCircleElement(LeftScroll, LeftScrollTop, '^');
	createCircleElement(rightScroll, rightScrollTop, '>');
	}
	} else {
	styleEl1.height = 22;
	styleEl1.width = middleWidth - LeftScroll;
	styleEl1.top = LeftScrollTop - 22;
	styleEl1.type = 2;
	styleEl2.width = rightScroll - middleWidth + 5;
        if (rightScrollTop > LeftScrollTop) {
	styleEl2.height = rightScrollTop - LeftScrollTop + 22;
	styleEl2.type = 5;
	styleEl2.top = LeftScrollTop - 22;
	createCircleElement(LeftScroll, LeftScrollTop, '^');
	createCircleElement(rightScroll, rightScrollTop, '>');
	} else {
	styleEl1.width = middleWidth - LeftScroll;
	styleEl2.height = LeftScrollTop - rightScrollTop - 22;
	styleEl2.type = 2;
	styleEl2.top = rightScrollTop;
	createCircleElement(LeftScroll, LeftScrollTop, '^');
	createCircleElement(rightScroll, rightScrollTop, '>');
	}
	}

        createLineElement(styleEl1);
	createLineElement(styleEl2);
	}

其次就是画圆圈的逻辑,其实逻辑大多数是相似的,这里就不一一赘述了

React代码

其实用React来编写时候,更加的方便,因为他会根据传入的值,动态的去渲染页面逻辑,这时在页面改动,需要动态更新画线点的时候会更加的方便

jsx 复制代码
import React from 'react';
import { DownOutlined, UpOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons';

function CreateLineElement({ width, height, type, left, top }) {
	let style = {
		width: width + 'px',
		height: height + 'px',
		position: 'absolute',
		left: left + 'px',
		top: top + 'px',
	};
	if (type === 1) {
		style = { ...style, borderBottom: '2px dashed #ff5b4d', borderRight: '2px dashed #ff5b4d' };
	} else if (type === 2) {
		style = { ...style, borderTop: '2px dashed #ff5b4d', borderLeft: '2px dashed #ff5b4d' };
	} else if (type === 3) {
		style = { ...style, borderTop: '2px dashed #ff5b4d' };
	} else if (type === 4) {
		style = { ...style, borderTop: '2px dashed #ff5b4d', borderRight: '2px dashed #ff5b4d' };
	} else if (type === 5) {
		style = { ...style, borderBottom: '2px dashed #ff5b4d', borderLeft: '2px dashed #ff5b4d' };
	}

	return <div className='render-line-hrl' style={style}></div>;
}
function CreateCircleElement({ left, top, content }) {
	const style = {
		top: top - 7.5 + 'px',
		left: left - 7.5 + 'px',
		fontSize: 12,
		color: '#fff',
		display: 'flex',
		justifyContent: 'center',
		alignItems: 'center',
		width: '15px',
		height: '15px',
		borderRadius: '50%',
		position: 'absolute',
		backgroundColor: '#ff5b4d',
		zIndex: '999',
	};

	return (
		<div className='render-circle-hrl' style={style}>
			{content}
		</div>
	);
}
function RenderElement({ leftEl, rightEl, type, boxEl }) {
	const { LeftScroll, LeftScrollTop } = leftEl;
	const { rightScroll, rightScrollTop } = rightEl;

	let circleLeftContent = '';
	let circleRightContent = '';

	const middleWidth = boxEl?.clientWidth / 2;
	const styleEl1 = {
		height: 0,
		width: 0,
		type: 1,
		left: LeftScroll,
		top: 0,
	};
	const styleEl2 = {
		height: 0,
		width: 0,
		type: 3,
		left: middleWidth,
		top: 0,
	};

	if (type === 1) {
		if (rightScrollTop > LeftScrollTop) {
			styleEl1.height = rightScrollTop - LeftScrollTop;
			styleEl2.height = rightScrollTop - LeftScrollTop;
			styleEl1.top = LeftScrollTop;
			styleEl2.top = LeftScrollTop + styleEl2.height;
			styleEl1.type = 4;
			styleEl2.type = 3;
		} else {
			styleEl1.height = LeftScrollTop - rightScrollTop;
			styleEl2.height = LeftScrollTop - rightScrollTop;
			styleEl1.top = LeftScrollTop - styleEl1.height;
			styleEl2.top = LeftScrollTop - styleEl2.height;
		}
		circleLeftContent = <DownOutlined />;
		circleRightContent = <RightOutlined />;
		styleEl1.width = middleWidth - LeftScroll;
		styleEl2.width = rightScroll - middleWidth;
	} else if (type === 2) {
		styleEl1.height = 22;
		styleEl1.width = middleWidth - LeftScroll;
		styleEl2.width = rightScroll - middleWidth + 5;
		styleEl1.top = LeftScrollTop;
		styleEl1.type = 5;
		circleLeftContent = <UpOutlined />;
		circleRightContent = <RightOutlined />;
		if (rightScrollTop > LeftScrollTop) {
			styleEl2.height = rightScrollTop - LeftScrollTop - 22;
			styleEl2.width = rightScroll - middleWidth + 5;
			styleEl2.type = 5;
			styleEl2.top = LeftScrollTop + 22;
		} else {
			styleEl2.height = LeftScrollTop - rightScrollTop + 22;
			styleEl2.type = 2;
			styleEl2.top = rightScrollTop;
		}
	} else {
		styleEl1.height = 22;
		styleEl1.width = middleWidth - LeftScroll;
		styleEl1.top = LeftScrollTop - 22;
		styleEl1.type = 2;
		styleEl2.width = rightScroll - middleWidth + 5;
		circleLeftContent = <UpOutlined />;
		circleRightContent = <RightOutlined />;
		if (rightScrollTop > LeftScrollTop) {
			styleEl2.height = rightScrollTop - LeftScrollTop + 22;
			styleEl2.type = 5;
			styleEl2.top = LeftScrollTop - 22;
		} else {
			styleEl1.width = middleWidth - LeftScroll;
			styleEl2.height = LeftScrollTop - rightScrollTop - 22;
			styleEl2.type = 2;
			styleEl2.top = rightScrollTop;
		}
	}
	return (
		<>
			<CreateLineElement {...styleEl1} />
			<CreateCircleElement left={LeftScroll} top={LeftScrollTop} content={circleLeftContent} />
			<CreateLineElement {...styleEl2} />
			<CreateCircleElement left={rightScroll} top={rightScrollTop} content={circleRightContent} />
		</>
	);
}

function Render({ renderList, type, boxEl }) {
	return renderList.map((item, index) => {
		return <RenderElement leftEl={item.leftEl} key={index} rightEl={item.rightEl} type={item.type} boxEl={boxEl} />;
	});
}

export default Render;

因为要画的线可能不止一条,所以传入一个数组来渲染,同时使用useState来传入数据,可以达到动态删除旧的元素和渲染新的元素

总结

其实在我实际开发中,画线的技术倒不是一个难点,只是想到动态添加div去渲染有点想不到(有没有可能只有我想不出来)

至于如何取到offsetTop和offsetLeft,有很多办法,我推荐的是两个对比点使用相同的类名,在不同的父节点中取到相同的类名,这样可以提高一下复用性。

相关推荐
犹豫的果冻布丁1 小时前
从零给 DeepSeek Harness 写一个壁纸皮肤插件(已开源)
前端·后端
漏刻有时1 小时前
数据可视化Three.js 3D 地图实战:单文件原生实现省域区县拉伸建模
前端
会说话的番茄1 小时前
AI 满嘴跑火车怎么办?给它配个"小抄"
前端·aigc
计算机魔术师1 小时前
AI 权力集中辩论实录:从「token 工厂」到「唯一幸存者」
前端
eric-sjq2 小时前
WanlyFrontend 中文声明式前端语言完全指南:让 0.6B 模型写出漂亮网页
前端
Full Stack Developme2 小时前
跨站请求伪造 (CSRF) 是什么 设计及工作原理
前端·okhttp·csrf
MoSTChillax2 小时前
UI规范设计:从视觉到交互的全面指南
前端·ui·产品经理·设计规范·vibecoding
雪碧聊技术2 小时前
Vue + Element Plus 实现文本溢出显示省略号及悬浮提示
前端·javascript·css·vue.js·文本溢出省略
eric-sjq2 小时前
0.6B 前端生成模型本地部署实战:消费级显卡跑通 WanlyFrontend 全流程
前端·本地部署
小妖6663 小时前
设置了 box-sizing: border-box; 不管用,下边框还是被挤压没了
前端·css·html