Files
TrueGrowth/test.html

346 lines
9.1 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>自定义可拖拽缩放文本框 Demo</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%);
color: #0f172a;
padding: 40px 20px;
}
.wrap {
max-width: 960px;
margin: 0 auto;
}
h1 {
margin: 0 0 12px;
font-size: 28px;
}
p.desc {
margin: 0 0 24px;
color: #475569;
line-height: 1.7;
}
.panel {
background: rgba(255,255,255,0.86);
border: 1px solid rgba(148,163,184,0.25);
border-radius: 20px;
box-shadow: 0 18px 50px rgba(15,23,42,0.08);
padding: 24px;
backdrop-filter: blur(8px);
}
.toolbar {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: center;
margin-bottom: 18px;
color: #334155;
font-size: 14px;
}
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
border-radius: 999px;
background: #eef2ff;
border: 1px solid #c7d2fe;
}
.resizable-box {
--box-width: 520px;
--box-height: 180px;
--min-width: 280px;
--min-height: 120px;
position: relative;
width: var(--box-width);
height: var(--box-height);
min-width: var(--min-width);
min-height: var(--min-height);
max-width: min(92vw, 900px);
border: 1px solid #cbd5e1;
border-radius: 18px;
background: white;
box-shadow: 0 10px 25px rgba(15, 23, 42, 0.08);
overflow: hidden;
transition: box-shadow 0.15s ease;
user-select: none;
}
.resizable-box.active {
box-shadow: 0 0 0 3px rgba(99,102,241,0.14), 0 18px 40px rgba(15,23,42,0.12);
}
.textbox {
width: 100%;
height: 100%;
overflow: auto;
padding: 18px 20px;
outline: none;
line-height: 1.8;
color: #1e293b;
font-size: 15px;
white-space: pre-wrap;
word-break: break-word;
}
.textbox:focus {
background: #fcfcff;
}
.handle {
position: absolute;
z-index: 2;
background: transparent;
}
.handle-right {
top: 8px;
right: 0;
width: 14px;
height: calc(100% - 24px);
cursor: ew-resize;
}
.handle-bottom {
left: 8px;
bottom: 0;
width: calc(100% - 24px);
height: 14px;
cursor: ns-resize;
}
.handle-corner {
right: 0;
bottom: 0;
width: 18px;
height: 18px;
cursor: nwse-resize;
}
.handle-corner::before {
content: "";
position: absolute;
right: 4px;
bottom: 4px;
width: 10px;
height: 10px;
border-right: 2px solid #94a3b8;
border-bottom: 2px solid #94a3b8;
border-bottom-right-radius: 3px;
}
.note {
margin-top: 16px;
font-size: 13px;
color: #64748b;
line-height: 1.7;
}
.stats {
margin-top: 14px;
display: flex;
flex-wrap: wrap;
gap: 10px;
font-size: 13px;
color: #334155;
}
.stats span {
padding: 6px 10px;
border-radius: 10px;
background: #f8fafc;
border: 1px solid #e2e8f0;
}
@media (max-width: 640px) {
.resizable-box {
width: min(100%, 520px);
}
}
</style>
</head>
<body>
<div class="wrap">
<h1>自定义可缩放文本框</h1>
<p class="desc">
文本框创建时有默认高度,内容较多时会出现滚动条。用户可以拖动右侧、底部和右下角来分别进行横向、纵向、对角缩放。
纵向放大时,最大高度会自动限制为刚好包住全部文字内容,不会再继续超过内容高度。
</p>
<div class="panel">
<div class="toolbar">
<span class="badge">默认高度180px</span>
<span class="badge">支持横向拖放</span>
<span class="badge">支持纵向拖放</span>
<span class="badge">支持对角拖放</span>
</div>
<div class="resizable-box" id="box">
<div class="textbox" id="textbox" contenteditable="true">
这是一个自定义的文本框 demo。你可以直接编辑这里的内容也可以拖动文本框边缘来调整大小。
需求点包括:
1. 新建时指定默认高度;
2. 内容很多时显示滚动条;
3. 用户既可以放大,也可以缩小;
4. 支持横向拖动;
5. 支持纵向拖动;
6. 支持右下角对角拖动;
7. 高度向上拖大时,不能超过文字实际内容所需高度。
为了更容易观察效果,这里特意放入了比较多的文字。
当文本框高度小于内容高度时,内部会出现滚动条;
当你继续向下拉大文本框时,会在“刚好展示完整内容”的高度停止。
你还可以尝试:
- 把宽度缩窄,这样文字会自动换行,内容总高度会变大;
- 再向下拉高文本框,这时最大高度会重新根据当前宽度下的内容高度计算;
- 修改文字内容后,最大高度也会动态更新。
这类交互常见于富文本编辑器、备注输入框、聊天草稿区和可配置后台表单中。
</div>
<div class="handle handle-right" data-dir="x"></div>
<div class="handle handle-bottom" data-dir="y"></div>
<div class="handle handle-corner" data-dir="xy"></div>
</div>
<div class="stats">
<span id="sizeInfo">当前尺寸520 × 180</span>
<span id="contentInfo">内容最大高度:--</span>
</div>
<div class="note">
提示:这个 demo 没有使用原生 <code>resize</code>,而是自己实现了拖拽逻辑,因此可以精确控制“高度不能超过内容”的规则。
</div>
</div>
</div>
<script>
const box = document.getElementById('box');
const textbox = document.getElementById('textbox');
const sizeInfo = document.getElementById('sizeInfo');
const contentInfo = document.getElementById('contentInfo');
const MIN_WIDTH = 280;
const MIN_HEIGHT = 120;
const DEFAULT_WIDTH = 520;
const DEFAULT_HEIGHT = 180;
let state = null;
function getHorizontalLimit() {
const margin = 32;
return Math.min(900, window.innerWidth - margin);
}
function getContentMaxHeight() {
const prev = textbox.style.height;
textbox.style.height = 'auto';
const contentHeight = textbox.scrollHeight;
textbox.style.height = prev;
return Math.max(MIN_HEIGHT, Math.ceil(contentHeight));
}
function applySize(width, height) {
const maxWidth = Math.max(MIN_WIDTH, getHorizontalLimit());
const maxHeight = getContentMaxHeight();
const nextWidth = Math.max(MIN_WIDTH, Math.min(width, maxWidth));
const nextHeight = Math.max(MIN_HEIGHT, Math.min(height, maxHeight));
box.style.width = nextWidth + 'px';
box.style.height = nextHeight + 'px';
sizeInfo.textContent = `当前尺寸:${Math.round(nextWidth)} × ${Math.round(nextHeight)}`;
contentInfo.textContent = `内容最大高度:${Math.round(maxHeight)}px`;
}
function refresh() {
const width = box.getBoundingClientRect().width;
const height = box.getBoundingClientRect().height;
applySize(width, height);
}
function startResize(e) {
const dir = e.target.dataset.dir;
if (!dir) return;
e.preventDefault();
e.stopPropagation();
const rect = box.getBoundingClientRect();
state = {
dir,
startX: e.clientX,
startY: e.clientY,
startWidth: rect.width,
startHeight: rect.height,
};
box.classList.add('active');
document.body.style.cursor = getCursor(dir);
document.body.style.userSelect = 'none';
}
function getCursor(dir) {
if (dir === 'x') return 'ew-resize';
if (dir === 'y') return 'ns-resize';
return 'nwse-resize';
}
function onMove(e) {
if (!state) return;
const dx = e.clientX - state.startX;
const dy = e.clientY - state.startY;
let width = state.startWidth;
let height = state.startHeight;
if (state.dir.includes('x')) width = state.startWidth + dx;
if (state.dir.includes('y')) height = state.startHeight + dy;
applySize(width, height);
}
function stopResize() {
if (!state) return;
state = null;
box.classList.remove('active');
document.body.style.cursor = '';
document.body.style.userSelect = '';
refresh();
}
document.querySelectorAll('.handle').forEach(handle => {
handle.addEventListener('mousedown', startResize);
});
window.addEventListener('mousemove', onMove);
window.addEventListener('mouseup', stopResize);
window.addEventListener('resize', refresh);
textbox.addEventListener('input', refresh);
box.style.width = DEFAULT_WIDTH + 'px';
box.style.height = DEFAULT_HEIGHT + 'px';
refresh();
</script>
</body>
</html>