V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
px920906
V2EX  ›  程序员

观摩一段简单粗暴的 vue 代码

  •  
  •   px920906 ·
    mdpx · 2020-09-29 23:42:59 +08:00 · 5954 次点击
    这是一个创建于 1507 天前的主题,其中的信息可能已经有所发展或是发生改变。

    是一个 vue 的 updated 生命周期钩子:

      updated () {
        let tx = document.getElementsByClassName('textContent');
        for (let jj = 0; jj < tx.length; jj++) {
          while (tx[jj].offsetHeight < tx[jj].scrollHeight) {
            let ss = tx[jj].innerText;
            tx[jj].innerText = ss.substr(0, ss.length - 4);
            tx[jj].innerText += '...';
          }
        }
      }
    

    页面是一个展示作品的页面,估计是设计要求作品简介的文字所占高度不超过一定值,超出部分裁掉用三个点代替。 作者的做法是等 vue 拿到数据并重新渲染 dom 之后把页面上所有.textContent 元素里的文字逐字删到所占高度刚好不超过元素高度(.textContent 高度固定)。作品有多条,再加上其他数据更新导致的重新渲染,这段代码会执行多次。

    可能他 /她在开发和测试过程中没遇到文字很多的情况,现在我们遇到了一个两万字的数据,导致每次刷新页面至少需要 2 分钟,期间页面处于假死状态。用 chrome 性能面板录制了一下,耗时 4 分多钟,上个截图大伙感受一下:

    4.2min

    设置 innerText 本身就是会导致强制同步布局的耗时操作,再循环两万次……

    不过光指出问题不够,怎么解决?

    我自己还真想不出好办法……不过首先会把这段逻辑放在获取作品数据完成后的一个 nextTick 里,而不是 updated 钩子里。另一个是获取到数据后直接限制文字长度为固定值,但不好保证最后行数以及文字末尾在行末,肯定过不了设计的关。或者,两者结合,先限制为一个尽量短但足够的长度,再执行上面的逻辑。

    最后查到了-webkit-line-clamp这个 css 属性,感觉不考虑 IE 的话,就是它了。

    大家有什么好的方案?

    49 条回复    2020-10-01 09:00:25 +08:00
    azh7138m
        1
    azh7138m  
       2020-09-29 23:48:23 +08:00 via iPhone   ❤️ 2
    wangxiang
        2
    wangxiang  
       2020-09-29 23:56:17 +08:00   ❤️ 5
    . textContent {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    }
    NullData
        3
    NullData  
       2020-09-30 00:27:47 +08:00 via iPhone   ❤️ 2
    看了之后有一点未经验证的小想法,可能比较粗糙
    1.查找算法可以换成二分查找来提升性能
    2.每次计算元素高度不用 dom 树中的节点,用 document.createElement 新建一个元素,操作他来避免 dom 树的回流来提升性能。
    9yu
        4
    9yu  
       2020-09-30 00:37:44 +08:00 via Android
    css 实现。
    noe132
        5
    noe132  
       2020-09-30 01:09:15 +08:00
    我用类似思路实现过一个动态的多行带省略号的判断。
    这个计算只在加载时执行一次,不需要每次 update 都重复计算
    加上二分查找,通常不需要太多次就能计算出来
    woncode
        6
    woncode  
       2020-09-30 02:07:28 +08:00 via Android   ❤️ 1
    看不懂用 vue 怎么还要“document.getElementsByClassName”,那不是 jquery 时代的写法,现在的 mvvm 框架不是应该直接在模板去变量绑定,响应式更新吗
    Sapp
        7
    Sapp  
       2020-09-30 02:08:23 +08:00   ❤️ 1
    css 自带这个功能...
    Sapp
        8
    Sapp  
       2020-09-30 02:10:59 +08:00   ❤️ 1
    另外就算不用 css,也不应该这么操作,应该把需要这个操作的文字都封装从一个组件里传入,在这个组件 mounted 之后获取一下高度,然后计算字符数量,从 template 里直接 slice(0,xxx)。计算字符这个操作不需要渲染,直接在内存就能操作
    Sapp
        9
    Sapp  
       2020-09-30 02:14:28 +08:00
    最不济的方式也是直接给外层固定高度的元素设置超出的全部隐藏,然后获取一下里面高度,如果高度超出的话给外边添加一个 class,class 的内容是...,通过 position 定位看起来像结尾的 ...。哪有挨个操作 dom 这个骚操作
    cxe2v
        10
    cxe2v  
       2020-09-30 02:33:02 +08:00
    @Sapp 挨个操作不算骚,这个最骚的是每次循环减掉 4 个字然后看高度达标没,不达标继续循环
    Mutoo
        11
    Mutoo  
       2020-09-30 07:11:19 +08:00
    可以用二分法优化一下,O(logN),:doge:
    KuroNekoFan
        12
    KuroNekoFan  
       2020-09-30 07:53:16 +08:00 via iPhone   ❤️ 2
    这种就是很典型的只懂 vue 的前端开发者能干出来的事了……
    akakidz
        13
    akakidz  
       2020-09-30 08:41:54 +08:00
    这是后端写的 Vue 吧 :doge:
    f056917
        14
    f056917  
       2020-09-30 08:46:50 +08:00
    能用 CSS 实现的为什么要用 JS
    f056917
        15
    f056917  
       2020-09-30 08:47:36 +08:00
    单行:

    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    多行:

    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
    or:

    overflow:hidden;
    text-overflow:ellipsis;
    display:-webkit-box;
    -webkit-box-orient:vertical;
    -webkit-line-clamp:2;


    PS:兼容 IE 的话要设置宽度
    DOLLOR
        16
    DOLLOR  
       2020-09-30 08:47:44 +08:00
    这是 jQuery 程序员写的 vue 吧 :doge:
    diegozhu
        17
    diegozhu  
       2020-09-30 08:50:46 +08:00
    @Sapp 请教一下,mount 后的组件也不能保证算出来的高度就是实际的高度吧?涉及到 style,起码 font-size,line-height,word-spacing 不一样对 size 影响很大。
    treblex
        18
    treblex  
       2020-09-30 08:56:43 +08:00
    有些使用 css,必需要 js 修改的话,在请求接口之后 赋值到 data 之前操作,
    treblex
        19
    treblex  
       2020-09-30 08:57:01 +08:00
    @suke971219 #18 优先 css
    hbolive
        20
    hbolive  
       2020-09-30 08:57:24 +08:00
    这明显是 CSS 基础不牢。。
    Czzzzzzzzzzr
        21
    Czzzzzzzzzzr  
       2020-09-30 09:09:09 +08:00
    说实话我还从来没有想到过还可以循环来减长度。。
    KuroNekoFan
        22
    KuroNekoFan  
       2020-09-30 09:24:35 +08:00 via iPhone   ❤️ 1
    事实上在某些场景下需要精准获得字符占的视图尺寸的,也应该是由 canvas 的 2dcontext 来获取,这循环实在是很有创意
    gouflv
        23
    gouflv  
       2020-09-30 09:57:45 +08:00 via iPhone
    算文字大小可以用 textarea
    Chenamy2017
        24
    Chenamy2017  
       2020-09-30 10:01:58 +08:00
    这是 jQuery 程序员写的 vue 吧---哈哈哈,违背了 vue 的设计初衷。
    96412hj
        25
    96412hj  
       2020-09-30 10:04:55 +08:00
    @wangxiang #2 你这个会有一个问题,如果加一个展开按钮,放在...后面,层级调高,不太好看
    dddddd
        26
    dddddd  
       2020-09-30 10:05:54 +08:00
    做事不带脑子,建议转行
    codespots
        27
    codespots  
       2020-09-30 10:11:07 +08:00
    CSS 能解决的问题,偏要用 JS 解决
    xrr2016
        28
    xrr2016  
       2020-09-30 10:14:52 +08:00
    上面说的差不多了,还有一种方式是使用 documentFragment,对创建的 fragment 进行操作,完成后一次性用 innerHTML 插入到页面上。
    rbq123456
        29
    rbq123456  
       2020-09-30 10:18:05 +08:00
    老实说,写 vue 这么久以来,再也没用过 getElementsByClassName 这个东西
    hackyuan
        30
    hackyuan  
       2020-09-30 10:23:27 +08:00   ❤️ 1
    vue 表示我不接这个锅
    supuwoerc
        31
    supuwoerc  
       2020-09-30 10:24:23 +08:00
    css 解决,再不济也是{{xxxx.slice(m,n)}}
    Torpedo
        32
    Torpedo  
       2020-09-30 10:26:10 +08:00
    你这个不就相当于一个组件,改了全局的属性么。。。。

    就算用 js,也应该是封装一个...组件,它只改自己就行了。
    而且可以所判断,innerText 变才改一次
    Hoshinokozo
        33
    Hoshinokozo  
       2020-09-30 10:28:50 +08:00   ❤️ 3
    1.能用 CSS 实现的为啥要用 js 实现?
    2.都手动操作 DOM 了那还要 vue 干啥? jQuery 不香吗?
    3.都啥年代还用 getElementBy API,querySelector API 不香吗?

    综上,统一 13 楼的疑问,这是后端写的 vue 吧?(:doge
    Sapp
        34
    Sapp  
       2020-09-30 10:32:12 +08:00
    @diegozhu 在页面做一个隐藏(绝对定位+负 index,不会引起页面重绘)的元素,渲染文本,然后读取这个 dom 的属性,读取的属性也是百分之百准确的,读取出来之后处以 line-height,就是行数,拿到行数用总文字初一下,取个整数,就是每行的文字了,然后减少几个文字(省略号的空间),设置这个 index,再在页面可见元素里渲染 `${msg.slice(0, index)}...`
    我记得以前做富本文编辑器就有这种骚操作,不过单独为了个省略号是真的不值得
    fengmumu
        35
    fengmumu  
       2020-09-30 11:48:36 +08:00
    @diegozhu getComputedStyle 这个可以获取到最后的计算属性
    fengmumu
        36
    fengmumu  
       2020-09-30 11:51:41 +08:00
    @noe132 要是浏览器尺寸改变的化 这边不触发更新,然后样式已经改变了,就有点尴尬了,如果可以这边还需要绑定一下浏览器窗口尺寸变化事件
    fengmumu
        37
    fengmumu  
       2020-09-30 11:54:28 +08:00
    @Sapp 可以先判断是不是出现滚动条,然后再取消滚动条展示,直接给覆盖上省略号
    flowfire
        38
    flowfire  
       2020-09-30 11:55:56 +08:00
    如果是我的话,可能会直接采取 2 楼所说的,用纯 CSS 实现 禁止换行 + 超出隐藏 + 自动显示三个点。
    =======
    如果只是想改进源代码,那原来的实现方式是可取的。
    为了防止字数过多导致重新渲染次数过多可以加一个补丁。
    比如:已知不管使用任何文字,任何字体,任何字间距,只要文字数量超过 X 个,一定会超出范围。
    那么第一步就是判断全文是否超过 X 字,超过的部分直接截断。
    这样不管字有多长,第一步就先把可能存在的过多的文字直接去掉了。
    剩下的部分使用原逻辑执行速度在可容忍范围之内。
    whorusq
        39
    whorusq  
       2020-09-30 13:37:27 +08:00
    一般以下两种:
    1. 前端 css 处理,考虑样式兼容;
    2. 根据字符串长度截断,vue 应该在 计算属性 或 过滤器里 处理
    sjhhjx0122
        40
    sjhhjx0122  
       2020-09-30 14:55:33 +08:00
    文字超出隐藏,一般前端肯定想到的是 css 实现,一般人真想不到这样实现
    weixiangzhe
        41
    weixiangzhe  
       2020-09-30 15:41:04 +08:00 via Android
    省略号用不了,可以用个 after 做个渐变的蒙层 很多公司这么整
    lixuda
        42
    lixuda  
       2020-09-30 15:50:13 +08:00
    @f056917 如果是 flex 布局,怎么写?
    tony1890
        43
    tony1890  
       2020-09-30 15:54:31 +08:00
    1. css 单行或多行
    2. 超过隐藏,加一个省略号覆盖
    3. 用 canvas 绘制文字,用 measureText 测绘,然后自己排版

    真心不喜欢这些需求,直接不许换行多好。
    pigzzz
        44
    pigzzz  
       2020-09-30 16:18:27 +08:00
    css 啊,兄弟,这是基础
    f056917
        45
    f056917  
       2020-09-30 16:27:06 +08:00
    @lixuda 在 flex 元素下面再建个子元素
    wangxiaoaer
        46
    wangxiaoaer  
       2020-09-30 16:36:57 +08:00 via iPhone
    @NullData 同意,如果必须通过计算字符个数,那么无脑遍历显然效率效率太低,二分法可以很大程度加快速度。甚至设置不同的区间用采用不同的分法。
    zouri
        47
    zouri  
       2020-09-30 17:23:02 +08:00
    这段代码我愿称之为 "什么这个特性那个特性,我就一把循环,搞不定就再上一层" 之术
    iyangyuan
        48
    iyangyuan  
       2020-09-30 22:33:17 +08:00 via iPhone
    实在不行高度写死,用 absolute right 0 bottom 0 做一个假的...也比这强吧
    dd0754
        49
    dd0754  
       2020-10-01 09:00:25 +08:00 via iPhone
    这不是前端写的吧,这种情况前端一般用 css
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5427 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 39ms · UTC 07:03 · PVG 15:03 · LAX 23:03 · JFK 02:03
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.