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

功能说明

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

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

实现方法

当然可能还有更好的方法,会有更高的性能,此瑾代表为大家提供一个思路而已,同时代码本质只是一个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,有很多办法,我推荐的是两个对比点使用相同的类名,在不同的父节点中取到相同的类名,这样可以提高一下复用性。

相关推荐
m0_748247551 小时前
Web 应用项目开发全流程解析与实战经验分享
开发语言·前端·php
m0_748255022 小时前
前端常用算法集合
前端·算法
真的很上进2 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
web130933203982 小时前
vue elementUI form组件动态添加el-form-item并且动态添加rules必填项校验方法
前端·vue.js·elementui
NiNg_1_2342 小时前
Echarts连接数据库,实时绘制图表详解
前端·数据库·echarts
如若1233 小时前
对文件内的文件名生成目录,方便查阅
java·前端·python
滚雪球~4 小时前
npm error code ETIMEDOUT
前端·npm·node.js
沙漏无语4 小时前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport4 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
brrdg_sefg4 小时前
WEB 漏洞 - 文件包含漏洞深度解析
前端·网络·安全