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

功能说明

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

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

实现方法

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

相关推荐
永日456704 分钟前
学习日记-HTML-day51-9.9
前端·学习·html
狗头大军之江苏分军20 分钟前
iPhone 17 vs iPhone 17 Pro:到底差在哪?买前别被忽悠了
前端
小林coding21 分钟前
再也不怕面试了!程序员 AI 面试练习神器终于上线了
前端·后端·面试
文心快码BaiduComate33 分钟前
WAVE SUMMIT深度学习开发者大会2025举行 文心大模型X1.1发布
前端·后端·程序员
babytiger33 分钟前
python 通过selenium调用chrome浏览器
前端·chrome
passer98140 分钟前
基于webpack的场景解决
前端·webpack
奶昔不会射手1 小时前
css3之grid布局
前端·css·css3
举个栗子dhy1 小时前
解决在父元素上同时使用 onMouseEnter和 onMouseLeave时导致下拉菜单无法正常展开或者提前收起问题
前端·javascript·react.js
Coding_Doggy1 小时前
苍穹外卖前端Day1 | vue基础、Axios、路由vue-router、状态管理vuex、TypeScript
前端