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

功能说明

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

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

实现方法

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

相关推荐
qiyi.sky4 分钟前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~8 分钟前
分析JS Crash(进程崩溃)
java·前端·javascript
安冬的码畜日常17 分钟前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
l1x1n01 小时前
No.3 笔记 | Web安全基础:Web1.0 - 3.0 发展史
前端·http·html
昨天;明天。今天。1 小时前
案例-任务清单
前端·javascript·css
zqx_72 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己2 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称3 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色3 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2343 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js