1. 起步
最近在工作中遇到了一个需求,需求很简单就是一个表单的数据提交,但是设计给出的表单样式大概是下面这样的:
一个信封的样式,组件使用的是antd的多行文本组件
TextArea
,唯一麻烦的一点就是如何实现文字的下划线效果,拿到需求后最开始想到就是js➕绝对定位一把嗦,但是由于项目要适配h5端,由于使用js的方式会涉及到到计算,在适配不同机型方面总会有点误差,最后经过简单的调研总结出了两种实现方式:
2. 实现思路
方案 | 备注 |
---|---|
方案一:使用js动态计算 | 通过计算行高和容器总高度,生成横线dom并定位 |
方案二:css属性-background | 使用background属性,将横线作为容器的背景展示 |
提前说明,最终项目使用的方案是第二种,实现方式非常巧妙和简单,如果你也遇到了类似的需求,可以直接参考方案二,但是由于第一种方案也做过实现,这里也顺便记录下:
2.1 方案一:使用js动态计算
既然是使用js➕绝对定位的方式,首先第一步肯定是要先搞定初始样式,我们最开始的代码是这样的
import { Input } from "antd";
import styles from "./index.module.less";
import { useEffect, useRef, useState } from "react";
const defaultValue = `亲爱的小牛:
你好!最近工作还顺利吗?希望你在忙碌的代码世界中依然保持着那份热情和动力。
作为同行,我深知程序员这条路上的挑战和压力。有时候,面对复杂的逻辑、棘手的Bug,或是不断变化的技术栈,难免会感到疲惫和迷茫。但正是这些挑战,让我们不断成长,也让我们更加坚定地走在技术的道路上。
我想和你分享一句话:“代码如人生,调试是常态。”无论是编程还是生活,遇到问题并不可怕,重要的是我们如何面对和解决它们。每一次的调试和优化,都是我们进步的机会。相信你也有同样的感受吧?
最近我在学习一些新的技术栈,虽然刚开始有些吃力,但慢慢发现,只要保持耐心和好奇心,总能找到突破的方向。如果你也在学习或尝试新的东西,不妨一起交流心得,互相鼓励。毕竟,技术的道路上,有伙伴同行总是更有动力。
最后,希望我们都能在编程的世界里不断突破自我,写出更优雅的代码,解决更有意义的问题。无论遇到什么困难,记得我们都在同一条路上,一起加油!
期待你的回复,也期待我们未来的合作与交流。
祝好,
M木
2025年1月1日`;
function TextAreaLine() {
const { TextArea } = Input;
const [value, setValue] = useState(defaultValue);
useEffect(() => {}, []);
return (
<div className={styles.wrapper}>
<h1 style={{ textAlign: "center" }}>信封样式</h1>
<div className={styles["textare"]}>
<TextArea
autoSize={{ minRows: 18 }}
showCount
value={value}
onChange={(e) => setValue(e.target.value)}
maxLength={1000}
/>
<div className={styles.line}></div>
</div>
</div>
);
}
export default TextAreaLine;
简单的过一遍代码,我们的输入框容器的类名是在第24行,在css样式用,我们定义一个类名.line
来表示文本下面的横线,通过绝对定位的方式来定位到容器中,所以我们的css代码是这样的:
.wrapper {
width: 50vw;
margin: 50px auto;
.textare {
position: relative;
:global {
.ant-input {
line-height: 30px;
}
}
}
.line {
height: 1px;
width: 100%;
background: #eee;
position: absolute;
z-index: 999;
top: 30px;
}
}
做完了这些准备工作之后,我们预览我们的页面,就能看到我们的第一条线了。
后面要做的就比较简单了
- 在
useEffect
钩子中获取文本域容器的高度 - 使用文本与的高度➗约定好的行高计算出需要生成横线的数量
- 遍历生成横元素,通过索引值计算元素绝对定位的top值
- 使用
MutationObserver
Api监听dom元素的高度变化后重新生成横线
这里直接贴上实现后的代码
import { Input } from "antd";
import styles from "./index.module.less";
import { ReactNode, useEffect, useState } from "react";
const defaultValue = `亲爱的小牛:
你好!最近工作还顺利吗?希望你在忙碌的代码世界中依然保持着那份热情和动力。
作为同行,我深知程序员这条路上的挑战和压力。有时候,面对复杂的逻辑、棘手的Bug,或是不断变化的技术栈,难免会感到疲惫和迷茫。但正是这些挑战,让我们不断成长,也让我们更加坚定地走在技术的道路上。
我想和你分享一句话:“代码如人生,调试是常态。”无论是编程还是生活,遇到问题并不可怕,重要的是我们如何面对和解决它们。每一次的调试和优化,都是我们进步的机会。相信你也有同样的感受吧?
最近我在学习一些新的技术栈,虽然刚开始有些吃力,但慢慢发现,只要保持耐心和好奇心,总能找到突破的方向。如果你也在学习或尝试新的东西,不妨一起交流心得,互相鼓励。毕竟,技术的道路上,有伙伴同行总是更有动力。
最后,希望我们都能在编程的世界里不断突破自我,写出更优雅的代码,解决更有意义的问题。无论遇到什么困难,记得我们都在同一条路上,一起加油!
期待你的回复,也期待我们未来的合作与交流。
祝好,
M木
2025年1月1日`;
function TextAreaLine() {
const { TextArea } = Input;
const [value, setValue] = useState(defaultValue);
const [lines, setLines] = useState<ReactNode[]>([]);
useEffect(() => {
// 获取textare容器下的输入框组件
const target = document.querySelector(`.${styles["textare"]} .ant-input`);
// 生成横线方法
const generateLines = (wrapperHeight: number, lineHeight: number = 30) => {
// 需要生成的数量
const lineCount = Math.floor(wrapperHeight / lineHeight);
const lineDoms = Array(lineCount)
.fill(0)
.map((_, index) => (
<div
className={styles.line}
key={index}
style={{ top: (index + 1) * lineHeight }}
></div>
));
setLines(lineDoms);
};
const observe = new MutationObserver((doms) => {
// 属性变化后会触发这里的代码,如果需要优化,可以在这里判断一下是否是style属性
generateLines((doms[0].target as HTMLElement).clientHeight)
});
// 只监听输入框的属性变化,也就是style属性
observe.observe(target, {
attributes: true,
childList: false,
subtree: false,
});
return () => {
observe.disconnect();
};
}, []);
return (
<div className={styles.wrapper}>
<h1 style={{ textAlign: "center" }}>信封样式</h1>
<div className={styles["textare"]}>
<TextArea
autoSize={{ minRows: 18 }}
showCount
value={value}
onChange={(e) => setValue(e.target.value)}
maxLength={1000}
/>
{lines.length !== 0 && lines}
</div>
</div>
);
}
export default TextAreaLine;
2.2 方案二:css属性-background
对于方案二,只要你的css用的足够好,并且你的脑洞够大,就不需要写这么多代码了,由于实现太简单了,这里我们先贴上全部代码
index.tsx
import { Input } from "antd";
import styles from "./index.module.less";
import { useState } from "react";
const defaultValue = `亲爱的小牛:
你好!最近工作还顺利吗?希望你在忙碌的代码世界中依然保持着那份热情和动力。
作为同行,我深知程序员这条路上的挑战和压力。有时候,面对复杂的逻辑、棘手的Bug,或是不断变化的技术栈,难免会感到疲惫和迷茫。但正是这些挑战,让我们不断成长,也让我们更加坚定地走在技术的道路上。
我想和你分享一句话:“代码如人生,调试是常态。”无论是编程还是生活,遇到问题并不可怕,重要的是我们如何面对和解决它们。每一次的调试和优化,都是我们进步的机会。相信你也有同样的感受吧?
最近我在学习一些新的技术栈,虽然刚开始有些吃力,但慢慢发现,只要保持耐心和好奇心,总能找到突破的方向。如果你也在学习或尝试新的东西,不妨一起交流心得,互相鼓励。毕竟,技术的道路上,有伙伴同行总是更有动力。
最后,希望我们都能在编程的世界里不断突破自我,写出更优雅的代码,解决更有意义的问题。无论遇到什么困难,记得我们都在同一条路上,一起加油!
期待你的回复,也期待我们未来的合作与交流。
祝好,
M木
2025年1月1日`;
function TextAreaLine() {
const { TextArea } = Input;
const [value, setValue] = useState(defaultValue);
return (
<div className={styles.wrapper}>
<h1 style={{ textAlign: "center" }}>信封样式</h1>
<TextArea
className={styles["textare"]}
autoSize={{ minRows: 18 }}
showCount
value={value}
onChange={(e) => setValue(e.target.value)}
maxLength={1000}
/>
</div>
);
}
export default TextAreaLine;
index.module.less
.wrapper {
width: 50vw;
margin: 50px auto;
.textare {
background: linear-gradient(to top, transparent 95%, #eee 5%);
background-size: 100% 30px;
border: 1px dashed orange;
padding-bottom: 30px;
:global {
.ant-input {
line-height: 30px;
}
}
}
没错只需要第5,第6两行代码就能直接实现我们最终的效果,只需要把上面的代码粘到你的编辑器中就能实现文章开始的效果了。那这两行代码是什么意思呢?
background: linear-gradient(to top, transparent 95%, #eee 5%);
表示为容器设置一个下往上的渐变背景,渐变的过度为透明95%,#eee色号5%。- 为了更清楚的看到效果,我们把渐变效果改为
background: linear-gradient(to top, orange 95%, #eee 5%);
,这时我们看到的效果是这样的
- 为了更清楚的看到效果,我们把渐变效果改为
可以看到橙色的颜色占了容器的95%,如果把橙色换成透明的换就只剩#eee的色号了,那么我们要如何使这个背景重复呢?使用background-size
调整背景的大小。
-
background-size: 100% 30px;
表示背景的横向是100%,纵向和我们的行高一致,于是就有变成了这样最后我们只需要把橙色换成透明色,我们的横线效果就简答实现了。