尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
拆解Qt滚动选择器:QListView与QScroller实现触摸惯性时间滚轮
简介这份资源是一套基于Qt框架的滚动数字选择器完整工程模拟手机闹钟设置时间时的上下滑动选择交互适合需要实现滑动选择、自定义控件及动态界面效果的Qt开发者参考。压缩包共三十一个文件总大小约一点八九兆包含七个C源文件、七个头文件、三个UI界面描述文件以及工程配置文件、构建脚本和可执行程序既便于阅读源码也可直接运行观察效果。其核心实现是数字滚动时字号与大小沿中心向外递减、选中数字高亮变色覆盖了滑动事件回调、图形绘制、动画平滑过渡等关键技术点。已有四百九十七人学习下载借助完整工程可快速掌握滚动选择器的触控反馈、视觉焦点设计以及动画系统并能在此基础上扩展为日期、音量等多种选择控件。1. 拆了一个 Qt 滚动选择器想要时间滚轮效果绕不开这几个核心动作做触屏设备上的时间设置时需求方往往直接抛一句“就像手机上那个滚轮”。这句话落到 Qt 里就是致命陷阱——因为 Qt 自带的 QDateTimeEdit 默认是上下箭头加键盘输入在触屏上又小又难点QCalendarWidget 则是一整张日历跟滚轮完全是两个物种。我在某工位机上就因为这个返过工方案评审时没人提原型一亮相反馈全是“改滚轮”。alarmText4.zip 这个资源包核心就是一个用 QAbstractItemView 自己绘制的滚动数字选择器支持时分设置、惯性滑动、选中高亮实现思路很典型复现也不难适合做嵌入式或者触屏应用的开发朋友。接下来按“原理→实现→时间联动→踩坑→进阶验证”的顺序拆。2. 滚动选择器选型QDateTimeEdit 为什么不顶用三个自绘方案的取舍先说清楚一个问题为什么不能直接拿 QDateTimeEdit 改改就上表面看它确实能选择数字但它的交互模型是“编辑”而不是“滚选”。相邻的数字靠上下箭头微调或者键盘输入在手指触摸的屏幕上箭头不到 20 像素宽点击精度要求极高。更麻烦的是它的弹出日历在触屏上渲染得像一张小邮票放大逻辑全部要自己处理。相比之下一个全屏宽度的滚轮列表手指滑动即可选择才符合触屏直觉。所以这里得自绘。自绘滚动选择器的主流方案有三条路我拆 alarmText4 时对比了很久方案核心机制优点缺点QListView 自绘 delegate复用 item 视图框架用滚动条偏移量做选中实现快滚动和选中分离内存可控样式约束多绘制 delegate 时需要注意像素对齐QGraphicsView QGraphicsItem场景中摆放数字项用动画驱动位置动画发力直观渲染自由需要手动管理 item 数量大量数字时内存开销大纯 QWidget paintEvent直接在窗体重绘所有数字无视图框架依赖代码最直白命中测试、滚动管理全部手动实现边界情况多alarmText4 走的是第三条路纯 QWidget 重绘。这么做的好处是依赖最少嵌入式环境哪怕没有完整的 Qt Quick 支持也能跑。坏处则是惯性滑动、手指按下命中、滚动边界全都要自己兜底。选型上的关键判断点在于滚动数字选择器本质上是一个“一维的、有边界的、逐项对齐的列表”。QAbstractItemView 这套框架天生就适合这种列表形态——它已经帮你处理了 item 的布局、命中测试和滚动条映射。与其全部手写不如借 QAbstractItemView 的骨架只偷它的数据模型和滚动机制绘制部分全部接管自己画。这正是我在拆 alarmText4 时最认可的地方它用 QListView 作为基类把选中逻辑嵌入滚动位置里滑动和绘制天然解耦后面做时间联动时省了很多事。2.1 为什么说“滚动即选中”是这个方案的设计核心普通列表的选中是一个状态滚动是另一个状态两者之间靠点击事件绑定。滚动选择器则相反列表滚到哪个位置哪个 item 就处于选中态。这背后的关键概念是“滚动偏移量”和“选中索引”之间的换算关系。常见做法是定义每个 item 的固定高度为 itemSize整个列表的 contentHeight itemSize * itemCount。滚动的总范围是 0 到 contentHeight - viewportHeight。选中索引通过滚动条 value 计算index int(float(scrollbar_value) / itemSize 0.5)这里0.5的作用是四舍五入到最近的一项保证滚动到 item 一半以上位置时选中项才切换。如果省掉这一步视觉上会出现“滚到两个数字中间但选中未变”的割裂感。我一般会让滚动条范围抬高一点留出上下各两个 item 的“缓冲带”这样首项和末项都能滚动到居中位置而不是只能停在顶部。这个设计在纯 paintEvent 里叫“内容外扩”在这套 QListView 方案里则通过调整滚动条最大值实现scrollBar-setRange(0, itemSize * itemCount - scrollBar-pageStep() itemSize);多出来的itemSize就是缓冲。首项居中时滚动条停在 0末项居中时滚动条停在最大值不会出现“滚不到头”的憋屈感。2.2 QListView 基座买的是什么单这里明确一下基座职责QListView 负责两件事第一是提供固定间隔的行布局第二是接收触摸、鼠标滚轮、方向键的输入并转成滚动条的移动。你接管的部分是 delegate 绘制和选中高亮逻辑。QListView 不会替你画渐变、放大当前项、画高亮滑块背景这些都必须自己写进 paint() 方法里。alarmText4 的做法是在 paint() 里按行号绘制文字再根据行号与选中索引的距离做放大缩小的字重过渡。这种“离中心越近字号越大”的效果是滚动选择器视觉上的灵魂代码不算复杂void SelectDelegate::paint(QPainter *painter, const QStyleOptionViewItem option, const QModelIndex index) const { painter-save(); painter-setRenderHint(QPainter::Antialiasing); const QString text index.data().toString(); const int centerY option.rect.center().y(); const int distance qAbs(centerY - option.widget-height() / 2); const int maxDistance option.widget-height() / 2; qreal scale 1.0 - distance / (qreal)maxDistance * 0.35f; QFont font option.font; font.setPixelSize(int(baseFontSize * scale) 1); QColor color (distance itemSize / 2) ? selectedTextColor : normalTextColor; painter-setFont(font); painter-setPen(color); painter-drawText(option.rect, Qt::AlignCenter, text); painter-restore(); }这段代码的逻辑在于根据当前绘制行与视口中心的距离来缩放字号。距离为 0 就是选中项字号最大颜色也最醒目越靠边缘的字越小颜色越淡。scale的下限是 0.65也就是边缘项至少保持 65% 的字号不会小到看不清。参数方面baseFontSize需要在初始化时根据屏幕高度调触屏设备一般 26~36 像素比较合适。itemSize建议不小于 46 像素否则手指滑动时视觉反馈太密选中项来回跳用户会觉得自己在玩一个失控的转盘。如果你发现边缘项和选中项的字号差异太小就把 0.35f 改成 0.5f差异太大就反过来往小调。这一段是滚动选择器手感的一个重要来源——没有这个缩放滚动时的纵深感和“循环滚轮”的暗示就全没了。3. 核心实现触摸惯性滑动与像素对齐滚动选择器的两座基石光有 QListView 和 delegate 还不够滚动选择器和普通列表最大的区别在于“触屏滑动得有惯性”。没有惯性的滚动是“拖一下停一下”在触屏设备上非常生涩。Qt 里实现惯性滑动的标准姿势是 QScroller它负责把手指的即时运动转换成一段带阻尼的减速动画直接驱动的对象是滚动条。alarmText4 里用了一句关键调用把 QScroller 挂载到滚动区域上QScroller::grabGesture(this-viewport(), QScroller::TouchGesture);QScroller::grabGesture有三个参数第三个参数是QScroller::TouchGesture它会把触摸手势识别成滚动手势。这里有个细节必须注意grab 的目标是viewport()而不是this。QListView 本身有视口和框架之分触摸事件走视口grab 错对象的话滑动完全没有反应。这个位置经常成为新手翻车的第一站。3.1 滚动模式与滚动手感参数QAbstractItemView 的默认滚动模式是按 item 跳转的ScrollPerItem一行一行地跳触屏滑动时感觉像在翻书。滚动选择器必须用像素级的ScrollPerPixellistView-setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); listView-setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); listView-setUniformItemSizes(true);ScrollPerPixel的意义在于让滚动条 value 以像素为单位变化滑动轨迹与手指位移一一对应。如果不加这一句即使有惯性数字滚动也是按整个 item 高度跳跃的——你就永远看不到“数字平滑掠过”的效果。setUniformItemSizes(true)则是一个性能开关告诉视图所有 item 尺寸相同布局时可以大幅跳过单个 item 尺寸计算对几百个数字项的列表收益明显。手感参数调节是键盘党最容易忽略、触屏党最容易抱怨的环节。默认的 QScroller 参数在嵌入式触屏上往往过钝或过滑我通常会在构造函数里强制覆盖一套QScrollerProperties props QScroller::scroller()-scrollerProperties(); props.setScrollMetric(QScrollerProperties::DecelerationFactor, 0.85); props.setScrollMetric(QScrollerProperties::DampingFactor, 0.55); props.setScrollMetric(QScrollerProperties::MaximumVelocity, 0.35); props.setScrollMetric(QScrollerProperties::SnapTime, 0.5); QScroller::scroller()-setScrollerProperties(props);四个参数的分工是DecelerationFactor控制减速快慢值越大减速越猛数值在 0.75~0.95 之间比较合理DampingFactor控制阻尼值大会让滚动更快停下来调到 0.5~0.6 手感最接近手机滚轮的“黏而不拖”MaximumVelocity限制最大速度防止甩太快飞过整屏SnapTime是滚动停止后回弹到最近 item 的时间单位是秒0.5 秒的回弹既顺滑又不突兀。一个容易被忽略的坑是QScrollerProperties 必须在 grabGesture 之后再设置顺序反了属性会被手势绑定时的默认值覆盖调了半天手感纹丝不动。踩过这个坑的人应该都知道那是一种什么体验——调参调到自我怀疑结果只是顺序问题。3.2 像素对齐让数字停在“鞍点”滚动选择器落停的位置必须保证选中项严格居中。这个动作靠 Qt 的信号做拦截QScroller在滚动结束后会触发QWidget::scrollBarValueChanged或者QScroller::stateChanged。我一般在stateChanged里监听ScrollerState::Inactive即滚动完全停下来然后计算当前滚动值到最近 item 中心点的偏移用动画补完最后这几帧。void ScrollerWidget::onStateChanged(QScroller::State newState) { if (newState QScroller::Inactive) { QScrollBar *sb verticalScrollBar(); int remainder sb-value() % itemSize; int target sb-value() - remainder; if (remainder itemSize / 2) { target itemSize; } snapAnimation-setDuration(180); snapAnimation-setStartValue(sb-value()); snapAnimation-setEndValue(target); snapAnimation-start(); } }remainder是当前滚动值对 itemSize 取模后的余数表示位置离上一个 item 的偏差。如果偏差超过一半说明视觉上更靠近下一个 item直接补到下一个否则退回到上一个。这段逻辑就是“像素对齐”的全部核心滚动结束后的位置必须是某个 item 的中心点而不是停在两个数字之间的缝里。snapAnimation用 QPropertyAnimation 驱动滚动条才是正确姿势。直接 setValue 跳到 target视觉是瞬移的没有回弹感。而 180 毫秒的动画刚好能模拟一个短暂的“定格回正”效果让滚轮的停止看起来更像是一种有阻尼的自然落点。这里有一个微妙的边界在触摸手势中用户手指还没有离开屏幕时QScroller处于Dragging状态此时千万不要打断滚动做对齐。一切对齐都留到Inactive之后处理否则会和手指的拖动互相掐架。如果你发现手指离开后数字总是错位半格大概率就是在这个信号时机上没有严格区分状态。4. 时间联动设置小时分钟与 AM/PM 的刷新机制附件包里的现成动作滚动选择器做的不是孤立的数字列表而是“时间”这个复合值。alarmText4 里的做法是三个滚轮并列左滚轮小时1~12 或 0~23中间滚轮分钟00~59右边滚轮 AM/PM12 小时制才有。三者之间有两个联动点分钟步进和 AM/PM 切换时的小时刷新逻辑。先看时间模型。时间选择不能只靠现成的 QTime 类因为滚轮需要离散的值序列QListint hourList; QListint minuteList; if (use24Hour) { for (int h 0; h 24; h) hourList.append(h); } else { for (int h 1; h 12; h) hourList.append(h); } for (int m 0; m 60; m minuteStep) minuteList.append(m);minuteStep是分钟滚轮的步进值常见是 1、5、15。用步进值而不是 60 个数字的原因有两个一是不需要精度为 1 分钟的场景下只显示00/15/30/45既清爽又防手滑误选二是业务需求里闹钟本身只需要 15 分钟粒度列表短了滚动距离也就短了用户体验会好很多。注意分钟列表生成时用m minuteStep不要用m % minuteStep 0去过滤后者在步进值不小的时候会导致最后一个值是 55 还是 60 的边界混乱。4.1 联动逻辑AM/PM 切换时小时列表的两种刷新方向12 小时制下小时列表固定是 1~12不会因为 AM/PM 变化而改变内容本身。真正的联动点是“当前选中小时值不变但要确保语义上仍是有效的小时”。这个看起来是不变的废话实际编码时有个容易无视的隐性问题如果用户滚到的是 12然后从 AM 切到 PM此时 12AM 和 12PM 在时间语义上是不同的时刻但小时数值没变所以滚轮指针不应该跳动。真正需要联动的是 24 小时制和 12 小时制之间的切换。比如当前是 24 小时制的 13 点切到 12 小时制时小时滚轮的值必须变成 1并且 PM 滚轮要同步拨到 PM。反过来12 小时制的 11PM 切到 24 小时制要变成 23。这套逻辑建议放到一个集中函数里void TimePicker::switchHourMode(bool to24h) { int oldHour currentHour(); int newHour oldHour; if (to24h !use24Hour) { newHour (oldHour % 12) (isPM ? 12 : 0); } else if (!to24h use24Hour) { bool pm oldHour 12; newHour (oldHour % 12 0) ? 12 : (oldHour % 12); setAMPM(pm); } populateHourList(to24h); setSelectedHour(newHour); }这段逻辑里% 12是 12 小时制转换的核心。一个数值上是 12 的小时取模后是 0要特殊处理回 12这个分支少写了就会出现“上午 12 点变 0 点”的闹剧。setAMPM和setSelectedHour分别是设置右侧滚轮位置和小时滚轮选中位置的方法它们内部会触发各自的滚轮重定位。我发现很多人写联动时只在“用户手动切 AM/PM”这个点去刷新小时却忽略了构造函数里初始化模式的转换也需要走同一个入口。同一个入口函数既服务用户操作也服务初始化流程是这类联动逻辑不出差错的关键习惯。4.2 时间滚轮的信号与槽封装让外部拿到干净的 QTime当三个滚轮各自独立处理滚动时外部组件拿到的时间必须是合成值。alarmText4 的做法是每个滚轮在scrollBarValueChanged信号里记录自己的当前值然后统一 emit 一个复合信号。void TimePicker::onWheelValueChanged() { int h hourList[getCurrentIndex(hourWheel)]; int m minuteList[getCurrentIndex(minuteWheel)]; QTime time(h, m); if (!use24Hour isPM) { time time.addSecs(12 * 3600); } emit timeChanged(time); }注意addSecs(12 * 3600)这段只对 12 小时制生效。如果当前是 12AM就不做任何加时因为 addSecs 会把 AM 的 12 点变成中午 12 点那就错了。正确的判断是isPM h ! 12才加 12 小时isPM h 12则不加因为 12PM 就是中午!isPM h 12要变成 0 点即12 % 12。这个信号封装一定要放在滚轮变更入口统一发不能放在单个滚轮的 delegate 绘制里发。delegate 的 paint 在滚动过程中会被高频调用如果 emit 信号在里面外部组件会收到几百次时间变化通知不仅卡顿还会造成外部状态被中间值污染。正确的通知时机是滚动结束且对齐完成后发一次。你可以在onStateChanged对齐调用之后再统一次调用emit timeChanged这样外部拿到的永远是稳定值。使用时的连接方式很简单connect(timePicker, TimePicker::timeChanged, this, [this](const QTime t) { timeLabel-setText(t.toString(hh:mm AP)); });外部不需要关心内部滚轮在哪个位置只需要处理一个个 QTime 值。这种封装的收益在项目后期会体现出来——UI 换主题、调整显示格式都不需要动到时间合成逻辑。5. 避坑与常见问题排查滚动选择器翻车记录五条血泪经验打包这一段是拆 alarmText4 时最容易卡住的地方。滚动选择器看着简单实际跑起来在触屏环境的问题一个接一个。以下五条都是我在实机调试中真实遇到过的问题现象、原因、解决方式按顺序写清楚可以直接对照排查。5.1 触摸滑动完全没有反应鼠标滚轮却正常现象鼠标滚轮能滚动列表但手指在触摸屏上滑动时列表纹丝不动。原因QScroller::grabGesture 绑定到了错误的父对象上。常见做法是写成QScroller::grabGesture(this, QScroller::TouchGesture)但触摸事件实际作用范围是 viewport不是 QListView 本身。绑定到this上手势系统根本收不到触摸事件。解决将 grab 目标改成this-viewport()。另外如果目标平台是 touch 设备需要确认QApplication::setAttribute(Qt::AA_EnableTouchAdjust),否则触摸的准确性在边缘位置会打折扣。5.2 滚动完成后数字停在了两个 item 的缝合线上现象手指松开后数字没有停在一个 item 的垂直中心而是停在两行数字的边界上看起来像是被切了一半。原因对齐逻辑只修了一次滚动偏移但在惯性滚动还未完全停止时scrollBar 的 value 又被后续的 inertia 动画继续更新了导致 snap 到错误的位置。本质是对齐的触发时机太早。解决严格以QScroller::Inactive作为对齐的唯一触发点。前面已经写了通过 stateChanged 判断Inactive再执行 snap 动画这里再补充一个防御措施snap 动画的 duration 必须大于 100 毫秒否则在低速滚动结束时几个像素的抖动会让 snap 反复触发循环。5.3 选中项高亮框和文字不同步文字居中但高亮偏了一个 item现象文字已经切换到了新数字但高亮背景还在上一个位置或者相反。原因滚动时 QListView 的选中索引由selectionModel管理而像素偏移与索引之间的换算没有同步。通常是因为 delegate 绘制时的 index 是从QModelIndex拿到的但选中状态却是从滚动条计算出的adjust()得到的两边一致却在滚动过程中一次只更新一边。解决在 delegate 的 paint 中高亮背景的判断不要用option.state QStyle::State_Selected一律根据像素偏移量实时计算。选中索引 滚动条 value 对 itemSize 取整再四舍五入绘制时用这个索引判断高亮保证画出来的东西和视觉位置永远一致。这个方法看着多算了一次整数除法实际开销可以忽略。5.4 信号在滚动过程中疯狂触发外部组件拿到的全是中间值现象外部 Label 在滚动过程中反复刷新显示的数字跳来跳去还会闪现00:00之类的边界值。原因滚动过程中 scrollBarValueChanged 每帧都会发外部组件的槽函数如果直接处理就会被每个中间值轮番轰炸。尤其时间合成的时候 QTime 对象构造开销虽然不大但几百次信号堆积在 UI 线程仍然是卡顿源。解决把信号触发时机收敛到“对齐完成”之后。具体做法是给每个滚轮的滚动事件只做一个标记对齐动画结束时统一 emit。这样外部组件只在最终值稳定时收到一次通知界面表现瞬间干净利落。配合 Qt::QueuedConnection可以进一步保证信号不会在滚轮的 paint 过程中被直接触发。5.5 长按列表弹出了上下文菜单现象在触屏上长按数字列表弹出一个右键菜单里面有编辑、删除之类的选项滚动选择器的体验直接被毁。原因QListView 默认的contextMenuPolicy是DefaultContextMenu,长按在某些平台上会被解释成右键触发。解决在初始化时强制关闭listView-setContextMenuPolicy(Qt::NoContextMenu); listView-setFocusPolicy(Qt::ClickFocus);Qt::ClickFocus的作用是让焦点在点击时进入这样后续的键盘操作才能第一时间作用到滚轮上避免焦点还停留在上一个输入框上导致方向键无效。这个细节很不起眼但很重要尤其配合 QScroller 用方向键做辅助操作时焦点不对就是暗折腾。6. 进阶验证与技巧用 QTest 写滚动定位测试覆盖手指滑动的回归滚动选择器这类交互组件人工验证靠手感觉但回归测试不能靠“我觉得”。我习惯把滚动选择器的定位逻辑单独抽成可测试的计算函数然后用 QTest 模拟滚动事件做自动化验证。这个做法特别适合滚动选择器这种“滚动值→选中索引→时间值”的链路一旦改了 delegate 绘制的参数至少有测试托底。先从组件里抽出一个纯计算函数int TimePicker::indexFromScrollValue(int scrollValue, int itemSize, int itemCount) { int raw (scrollValue itemSize / 2) / itemSize; int maxIndex itemCount - 1; return qBound(0, raw, maxIndex); }核心逻辑是对滚动值做加半个 itemSize 的进位再统一夹在0到itemCount-1之间。加法itemSize / 2是前面四舍五入的另一种写法数学上等价但更直观滚动值每超过一个 item 的一半就进位到下一个索引。qBound保证了边界项不会越界越界在 Qt 的世界里会导致访问不存在的索引轻则画不出来重则崩溃这行一定要写。测试代码用 QTest 模拟鼠标滚轮滚动整个列表void TestTimePicker::testScrollSelection() { TimePicker picker(24, 1); QTest::mousePress(picker.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(50, 50)); QTest::mouseMove(picker.viewport(), QPoint(50, 80), 20); QTest::mouseRelease(picker.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(50, 80)); QCOMPARE(picker.currentHour(), 3); }这里QTest::mousePress和mouseMove模拟了从按下到拖动的手指路径QTest 会自动把鼠标事件派发给视口但要注意mouseMove必须在按下之后调用顺序反过来手势系统会认为这不是一个滑动。QCOMPARE断言当前小时值是 3如果滚动对齐逻辑没错这个值应该稳定通过。这个测试的作用不仅是验证算法更重要的是当有人改了 delegate 的字号或者 itemSize 没有同步调这个测试会立刻变红。自动化之外有两个小技巧对最终手感影响很大。第一选中项的背景高亮用一个半透明圆角矩形而不是白底黑字视觉上更像现代移动端的滚轮背板绘制开销差距不大但视觉质感提升明显。第二首项和末项的“缓冲带”长度要大于三个 item 高度否则滚动到边界时数字首尾衔接的突兀感很强体验上像是撞到一堵墙。做完了这些之后还要做一次真机滑动手感确认。我一般会让设备横屏和竖屏各测一轮重点观察两件事手指快速甩一下滚轮是否能在 0.7 秒内完成减速并稳定慢速拖动时数字是否能做到 1:1 跟手。如果快速甩动后回弹过头把DampingFactor调大 0.1如果跟手度差先检查ScrollPerPixel是否真的生效再考虑调小MaximumVelocity。从那以后我每次写完滚动选择器都强制自己走一遍QTest 自动化跑通定位逻辑再真机滑一遍短距离和长距离。这两步做完基本不会再有“看着挺对一上手就不对劲”的玄学问题。希望这份拆解能帮你把这个资源快速落地别再被滚轮选择器折磨一晚上。本文还有配套的精品资源点击获取
RELATED

相关推荐

区域综合能源系统电气热能流计算的Matlab实现与调试经验

区域综合能源系统电气热能流计算的Matlab实现与调试经验

最近我总算把一个题目里写得很简单、实际做着很费头发的项目在 Matlab 里完整落地了:计及多能耦合的区域综合能源系统电气热能流计算。这个方向在论文里常被一笔带过,但真上手去搭代码时,你会发现电力潮流、天然气水力、热力管网三套物理方程…

📅 2026/10/13 10:33:34
t3code全栈开发实战:基于T3 Stack的类型安全应用搭建指南

t3code全栈开发实战:基于T3 Stack的类型安全应用搭建指南

t3code这个关键词,最近在开发者社区的搜索热度一直在涨。如果你第一眼看到它跟我一样有点懵,很正常——它既不是一个官方框架名,也不是某个具体项目的代号。我翻了大量资料、自己也动手从零搭了一套完整的全栈应用跑了一遍之后,可…

📅 2026/10/13 10:28:33
CSS white-space: break-spaces 深入拆解:换行、空格保留与自动折行

CSS white-space: break-spaces 深入拆解:换行、空格保留与自动折行

做日志展示和聊天消息渲染时,我常遇到一个怪事:后端返回一段带\n的文本,直接塞进div后,所有换行全部消失,一行挤成长龙;换上white-space: break-spaces后,换行回来了,超长文本也能自…

📅 2026/10/13 10:28:33
MORE NEWS

更多资讯

📰

商贸企业采购退货财务处理与管家婆系统操作指南

1. 业务场景解析:成本支出后退回的本质在商贸企业的日常经营中,经常会出现已付款采购的商品因质量问题、规格不符等原因需要退货的情况。这类业务在财务处理上涉及三个关键节点:前期已确认采购成本并完成付款商品实物退回供应商资金退回企业账…

📰

OpenPose 1.7.0 Windows GPU二进制包:开箱即用多人姿态估计

简介:本资源是OpenPose 1.7.0官方预编译二进制包的Windows GPU加速定制版,专为需快速部署人体姿态估计能力的开发者与计算机视觉初学者设计,解决在64位Windows环境下免编译、低门槛接入高精度多目标2D/3D关键点检测的技术难题,适用…

📰

Vim Python/C++开发配置:构建可调试的语言服务基础设施

1. 为什么“学Vim”最后都卡在插件配置上——一个被低估的入门断层很多人开始学Vim,是冲着“高效编辑”“终端原生”“极客范儿”去的,结果三小时后卡在.vimrc文件里动弹不得:装了YouCompleteMe,编译报错;配了vim-pyth…

📰

AI代码审查API集成的5个关键HTTP状态处理

1. 为什么“调用一次就完事”是 CI 中最危险的幻觉“我把 AI 代码审查 API 接进 CI 流程了,每次 PR 提交自动跑一遍,返回结果就标红绿灯——这不就是智能 Code Review 吗?”这是我过去两年在多个技术分享会、内部 DevOps 交流群和跨团队协作会…

📰

从量化选型到权限防线:137B编码模型端侧部署全记录

你能想象一台PC跑一个137B参数的编码模型吗?我说的不是远程调用某个云端API,而是权重真正落在本地硬盘和显存里,离线运行。这事放在两年前几乎是天方夜谭——137B参数光FP16权重就要274GB存储空间,单卡显存根本装不下,…

📰

目标跟踪评估指标详解:从中心位置误差到成功率曲线的MATLAB实现

做目标跟踪的同学应该都有过这种感觉:算法自己跑起来看着“挺稳的”,框跟得很顺,偶尔目标晃两下也能拉回来。但一旦放到公开评测集上跑一遍,看到排名表上那些指标,心里落差很大。这不是算法不行,而是你还没…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬