wenruo

wenruo 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

wenruo 收藏了文章 · 2020-11-18

文本内容超过N行折叠并显示“...查看全部”

本文发布于我的个人网站:https://wintc.top/article/58,转载请注明。

多行文本超过指定行数隐藏超出部分并显示“...查看全部”是一个常遇到的需求,网上也有人实现过类似的功能,不过还是想自己写写看,于是就写了一个Vue的组件,本文简单介绍一下实现思路。

遇到这个需求的同学可以尝试一下这个组件,支持npm安装使用:

组件地址:https://github.com/Lushenggang/vue-overflow-ellipsis
在线体验:https://wintc.top/laboratory/#/ellipsis

一、需求描述

        长度不定的一段文字,最多显示n行(比如3行),不超过n行正常显示;超过n行则在最后一行尾部显示“展开”或“查看全部”之类的按钮,点击按钮则展开显示全部内容,或者跳转到其它页面展示所有内容。

        预期效果如下:

多行文本超过指定行数折叠

二、实现原理

        纯CSS很难完美实现这个功能,所以还得借助JS来实现,实现思路大体相似,都是判断内容是否超过指定行数,超过则截取字符串的前x个字符,然后然后和“...查看全部”拼接在一起,这里的x即截取长度,需要动态计算。

        想通过上述方案实现,有几个问题需要解决:

    • 怎样判断文字是否超过指定行数
    • 如何计算字符串截取长度
    • 动态响应,包括响应页面布局变动、字符串变化、指定行数变化等

        下面具体研究一下这些问题。

1. 怎样判断一段文字是否超过指定行数?

        首先解决一个小问题:如何计算指定行数的高度?我首先想到的是使用textarea的rows属性,指定行数,然后计算textarea撑起的高度。另一个方法是将行高的计算值与行数相乘,即得到指定行数的高度,这个办法我没尝试过,但是想必可行。

        解决了指定行数高度的问题,计算一段文字是否超过指定行数就很容易了。我们可以将指定行数的textarea使用绝对定位absolute脱离文档流,放到文字的下方,然后通过文本容器的底部与textarea的底部相比较,如果文本容器的底部更靠下,说明超过指定行数。这个判断可以通过getBoundingClientRect接口获取到两个容器的位置、大小信息,然后比较位置信息中的bottom属性即可。

        可以这样设计DOM结构:

 <div class="ellipsis-container">
    <div class="textarea-container">
      <textarea rows="3" readonly tabindex="-1"></textarea>
    </div>
    {{ showContent }} <-- showContent表示字符串截取部分 --> 
    ... 查看更多
  </div>

        然后使用CSS控制textarea,使其脱离文档流并且不能被看到以及被触发鼠标事件等(textarea标签中的readonly以及tabIndex属性是必要的):

.ellipsis-container
  text-align left
  position relative
  line-height 1.5
  padding 0 !important
  .textarea-container
    position absolute
    left 0
    right 0
    pointer-events none
    opacity 0
    z-index -1
    textarea
      vertical-align middle
      padding 0
      resize none
      overflow hidden
      font-size inherit
      line-height inherit
      outline none
      border none

2.如何计算字符串截取长度x——双边逼近法(二分思想)

        只要可以判断一段文字是否超过指定行数,那我们就可以动态地尝试截取字符串,直到找到合适的截断长度x。这个长度满足从x的位置截断字符串,前半部分+“...查看全部”等文字刚好不会超出指定行数N,但是多截取一个字,则会超出N行。最直观的想法就是直接遍历,让x从0开始增长到显示文本总长度,对于每个x值,都计算一次文字是否超过N行,没超过则加继续遍历,超过则获得了合适的长度x - 1,跳出循环。当然也可以让x从文本总长度递减遍历。

        不过这里最大的问题在于浏览器的回流和重绘。因为我们每次截取字符串都需要浏览器重新渲染出来才能得到是否超过N行,这过程中就触发了浏览器的重绘或回流,每次循环都会触发一次。而对于正常的需求来说,假设N取值是3,那很可能每次计算会导致50次以上的重绘或回流,这中间消耗的性能还是非常大的,不小心可能就是几十毫秒甚至上百毫秒。这个计算过程应该在一个任务(即常说的”宏任务“)中完成,否则计算过程中会出现显示闪动的”异常“情况,所以可以说计算过程是阻塞的,因此计算的总时间一定要控制到非常低,即要减少计算的次数。

        可以考虑使用"双边逼近法"(或称”二分法“)查找合适的截取长度x,大大减少尝试的次数。第一次先以文本长度为截取长度,计算是否超过N行,没超过则停止计算;超过则取1/2长度进行截取,如果此时没超过N行,则在1/2长度到文本长度之间继续二分查找,如果超过则在0到1/2文本长度中继续二分查找。直到查找区间开始值与结束值相差为1,则开始值即为所求。具体实现可以看下文中的完整代码。

3.监听页面变动

        对于Vue项目来说,传入组件的字符串、行数等可能随时改变,可以watch这些属性变化,然后重新计算一次截取长度。另一方面,对于页面布局而言,可能会因为其它页面元素的增删或者样式改变,导致页面布局变动,影响到文本容器的宽度,此时也应该重新计算一次截取长度。

        监听文本容器宽度的变化,可以考虑使用ResizeObserver来监听,但是这个接口的兼容性不够好(IE各个版本都不支持),因此选择了一个npm库element-resize-detector来监测(非常好用👍)。

三、代码实现

        完整的代码实现如下:

<template>
  <div class="ellipsis-container">
    <div class="textarea-container" ref="shadow">
      <textarea :rows="rows" readonly tabindex="-1"></textarea>
    </div>
    {{ showContent }}
    <slot name="ellipsis" v-if="(textLength < content.length) || btnShow">
      {{ ellipsisText }}
      <span class="ellipsis-btn" @click="clickBtn">{{ btnText }}</span>
    </slot>
  </div>
</template>

<script> import resizeObserver from 'element-resize-detector'
const observer = resizeObserver()

export default {
  props: {
    content: {
      type: String,
      default: ''
    },
    btnText: {
      type: String,
      default: '展开'
    },
    ellipsisText: {
      type: String,
      default: '...'
    },
    rows: {
      type: Number,
      default: 6
    },
    btnShow: {
      type: Boolean,
      default: false
    },
  },
  data () {
    return {
      textLength: 0,
      beforeRefresh: null
    }
  },
  computed: {
    showContent () {
      const length = this.beforeRefresh ? this.content.length : this.textLength
      return this.content.substr(0, this.textLength)
    },
    watchData () { // 用一个计算属性来统一观察需要关注的属性变化
      return [this.content, this.btnText, this.ellipsisText, this.rows, this.btnShow]
    }
  },
  watch: {
    watchData: {
      immediate: true,
      handler () {
        this.refresh()
      }
    },
  },
  mounted () {
    // 监听尺寸变化
    observer.listenTo(this.$refs.shadow, () => this.refresh())
  },
  beforeDestroy () {
    observer.uninstall(this.$refs.shadow)
  },
  methods: {
    refresh () { // 计算截取长度,存储于textLength中
      this.beforeRefresh && this.beforeRefresh()
      let stopLoop = false
      this.beforeRefresh = () => stopLoop = true
      this.textLength = this.content.length
      const checkLoop = (start, end) => {
        if (stopLoop || start + 1 >= end) return
        const rect = this.$el.getBoundingClientRect()
        const shadowRect = this.$refs.shadow.getBoundingClientRect()
        const overflow = rect.bottom > shadowRect.bottom
        overflow ? (end = this.textLength) : (start = this.textLength)
        this.textLength = Math.floor((start + end) / 2)
        this.$nextTick(() => checkLoop(start, end))
      }
      this.$nextTick(() => checkLoop(0, this.textLength))
    },
    // 展开按钮点击事件向外部emit
    clickBtn (event) {
      this.$emit('click-btn', event)
    },
  }
} </script>

        在代码实现中refresh函数用于计算截取长度,在文本内容、rows属性等发生改变或者文本容器尺寸改变时将被调用。每次refresh调用会异步地递归调用多次checkLoop,refresh可能重新调用,新的refresh调用将结束之前的checkLoop的调用。

四、其它

1. 支持HTML串的考虑

        现在的实现方案并不支持内容是HTML文本,如果需要支持HTML文本,问题将复杂许多。主要在于HTML字符串的解析和截断,不像文本字字符串那么简单。不过或许可以借助浏览器的Range API 来实现截断位置的定位,Range的insertNode以及setStart接口可以将“...查看全部”插入到指定位置,而如果插入位置刚好符合需要,则可以通过Range.cloneContents()")接口取得截取HTML字符串的相关内容,理论上是可行的,不过具体细节以及处理效率得实践后才知道。

2. 减少浏览器回流的影响

        上述实现方案中,每一次截取都需要浏览器重新渲染DOM,即重绘。重绘的影响还比较小,而如果截取的字符串行数发生改变,还会引发文本容器的高度变化,这时候就会导致浏览器回流,而文本容器在文档流中,回流将会影响整个文档。

        想解决这个问题,可以使用一个脱离文档流的元素来进行字符串动态截断后的渲染与判断,布局就类似上述的textarea。因为不在文档流中,回流的影响范围就会减少到该元素自身。获得截断长度后再截断文本,渲染到真正的文本容器即可。本文仅作为一个简单的原理概述的示例,没有做这个处理,对具体细节感兴趣的同学,可以查看github仓库代码。

查看原文

wenruo 赞了文章 · 2020-11-18

文本内容超过N行折叠并显示“...查看全部”

本文发布于我的个人网站:https://wintc.top/article/58,转载请注明。

多行文本超过指定行数隐藏超出部分并显示“...查看全部”是一个常遇到的需求,网上也有人实现过类似的功能,不过还是想自己写写看,于是就写了一个Vue的组件,本文简单介绍一下实现思路。

遇到这个需求的同学可以尝试一下这个组件,支持npm安装使用:

组件地址:https://github.com/Lushenggang/vue-overflow-ellipsis
在线体验:https://wintc.top/laboratory/#/ellipsis

一、需求描述

        长度不定的一段文字,最多显示n行(比如3行),不超过n行正常显示;超过n行则在最后一行尾部显示“展开”或“查看全部”之类的按钮,点击按钮则展开显示全部内容,或者跳转到其它页面展示所有内容。

        预期效果如下:

多行文本超过指定行数折叠

二、实现原理

        纯CSS很难完美实现这个功能,所以还得借助JS来实现,实现思路大体相似,都是判断内容是否超过指定行数,超过则截取字符串的前x个字符,然后然后和“...查看全部”拼接在一起,这里的x即截取长度,需要动态计算。

        想通过上述方案实现,有几个问题需要解决:

    • 怎样判断文字是否超过指定行数
    • 如何计算字符串截取长度
    • 动态响应,包括响应页面布局变动、字符串变化、指定行数变化等

        下面具体研究一下这些问题。

1. 怎样判断一段文字是否超过指定行数?

        首先解决一个小问题:如何计算指定行数的高度?我首先想到的是使用textarea的rows属性,指定行数,然后计算textarea撑起的高度。另一个方法是将行高的计算值与行数相乘,即得到指定行数的高度,这个办法我没尝试过,但是想必可行。

        解决了指定行数高度的问题,计算一段文字是否超过指定行数就很容易了。我们可以将指定行数的textarea使用绝对定位absolute脱离文档流,放到文字的下方,然后通过文本容器的底部与textarea的底部相比较,如果文本容器的底部更靠下,说明超过指定行数。这个判断可以通过getBoundingClientRect接口获取到两个容器的位置、大小信息,然后比较位置信息中的bottom属性即可。

        可以这样设计DOM结构:

 <div class="ellipsis-container">
    <div class="textarea-container">
      <textarea rows="3" readonly tabindex="-1"></textarea>
    </div>
    {{ showContent }} <-- showContent表示字符串截取部分 --> 
    ... 查看更多
  </div>

        然后使用CSS控制textarea,使其脱离文档流并且不能被看到以及被触发鼠标事件等(textarea标签中的readonly以及tabIndex属性是必要的):

.ellipsis-container
  text-align left
  position relative
  line-height 1.5
  padding 0 !important
  .textarea-container
    position absolute
    left 0
    right 0
    pointer-events none
    opacity 0
    z-index -1
    textarea
      vertical-align middle
      padding 0
      resize none
      overflow hidden
      font-size inherit
      line-height inherit
      outline none
      border none

2.如何计算字符串截取长度x——双边逼近法(二分思想)

        只要可以判断一段文字是否超过指定行数,那我们就可以动态地尝试截取字符串,直到找到合适的截断长度x。这个长度满足从x的位置截断字符串,前半部分+“...查看全部”等文字刚好不会超出指定行数N,但是多截取一个字,则会超出N行。最直观的想法就是直接遍历,让x从0开始增长到显示文本总长度,对于每个x值,都计算一次文字是否超过N行,没超过则加继续遍历,超过则获得了合适的长度x - 1,跳出循环。当然也可以让x从文本总长度递减遍历。

        不过这里最大的问题在于浏览器的回流和重绘。因为我们每次截取字符串都需要浏览器重新渲染出来才能得到是否超过N行,这过程中就触发了浏览器的重绘或回流,每次循环都会触发一次。而对于正常的需求来说,假设N取值是3,那很可能每次计算会导致50次以上的重绘或回流,这中间消耗的性能还是非常大的,不小心可能就是几十毫秒甚至上百毫秒。这个计算过程应该在一个任务(即常说的”宏任务“)中完成,否则计算过程中会出现显示闪动的”异常“情况,所以可以说计算过程是阻塞的,因此计算的总时间一定要控制到非常低,即要减少计算的次数。

        可以考虑使用"双边逼近法"(或称”二分法“)查找合适的截取长度x,大大减少尝试的次数。第一次先以文本长度为截取长度,计算是否超过N行,没超过则停止计算;超过则取1/2长度进行截取,如果此时没超过N行,则在1/2长度到文本长度之间继续二分查找,如果超过则在0到1/2文本长度中继续二分查找。直到查找区间开始值与结束值相差为1,则开始值即为所求。具体实现可以看下文中的完整代码。

3.监听页面变动

        对于Vue项目来说,传入组件的字符串、行数等可能随时改变,可以watch这些属性变化,然后重新计算一次截取长度。另一方面,对于页面布局而言,可能会因为其它页面元素的增删或者样式改变,导致页面布局变动,影响到文本容器的宽度,此时也应该重新计算一次截取长度。

        监听文本容器宽度的变化,可以考虑使用ResizeObserver来监听,但是这个接口的兼容性不够好(IE各个版本都不支持),因此选择了一个npm库element-resize-detector来监测(非常好用👍)。

三、代码实现

        完整的代码实现如下:

<template>
  <div class="ellipsis-container">
    <div class="textarea-container" ref="shadow">
      <textarea :rows="rows" readonly tabindex="-1"></textarea>
    </div>
    {{ showContent }}
    <slot name="ellipsis" v-if="(textLength < content.length) || btnShow">
      {{ ellipsisText }}
      <span class="ellipsis-btn" @click="clickBtn">{{ btnText }}</span>
    </slot>
  </div>
</template>

<script> import resizeObserver from 'element-resize-detector'
const observer = resizeObserver()

export default {
  props: {
    content: {
      type: String,
      default: ''
    },
    btnText: {
      type: String,
      default: '展开'
    },
    ellipsisText: {
      type: String,
      default: '...'
    },
    rows: {
      type: Number,
      default: 6
    },
    btnShow: {
      type: Boolean,
      default: false
    },
  },
  data () {
    return {
      textLength: 0,
      beforeRefresh: null
    }
  },
  computed: {
    showContent () {
      const length = this.beforeRefresh ? this.content.length : this.textLength
      return this.content.substr(0, this.textLength)
    },
    watchData () { // 用一个计算属性来统一观察需要关注的属性变化
      return [this.content, this.btnText, this.ellipsisText, this.rows, this.btnShow]
    }
  },
  watch: {
    watchData: {
      immediate: true,
      handler () {
        this.refresh()
      }
    },
  },
  mounted () {
    // 监听尺寸变化
    observer.listenTo(this.$refs.shadow, () => this.refresh())
  },
  beforeDestroy () {
    observer.uninstall(this.$refs.shadow)
  },
  methods: {
    refresh () { // 计算截取长度,存储于textLength中
      this.beforeRefresh && this.beforeRefresh()
      let stopLoop = false
      this.beforeRefresh = () => stopLoop = true
      this.textLength = this.content.length
      const checkLoop = (start, end) => {
        if (stopLoop || start + 1 >= end) return
        const rect = this.$el.getBoundingClientRect()
        const shadowRect = this.$refs.shadow.getBoundingClientRect()
        const overflow = rect.bottom > shadowRect.bottom
        overflow ? (end = this.textLength) : (start = this.textLength)
        this.textLength = Math.floor((start + end) / 2)
        this.$nextTick(() => checkLoop(start, end))
      }
      this.$nextTick(() => checkLoop(0, this.textLength))
    },
    // 展开按钮点击事件向外部emit
    clickBtn (event) {
      this.$emit('click-btn', event)
    },
  }
} </script>

        在代码实现中refresh函数用于计算截取长度,在文本内容、rows属性等发生改变或者文本容器尺寸改变时将被调用。每次refresh调用会异步地递归调用多次checkLoop,refresh可能重新调用,新的refresh调用将结束之前的checkLoop的调用。

四、其它

1. 支持HTML串的考虑

        现在的实现方案并不支持内容是HTML文本,如果需要支持HTML文本,问题将复杂许多。主要在于HTML字符串的解析和截断,不像文本字字符串那么简单。不过或许可以借助浏览器的Range API 来实现截断位置的定位,Range的insertNode以及setStart接口可以将“...查看全部”插入到指定位置,而如果插入位置刚好符合需要,则可以通过Range.cloneContents()")接口取得截取HTML字符串的相关内容,理论上是可行的,不过具体细节以及处理效率得实践后才知道。

2. 减少浏览器回流的影响

        上述实现方案中,每一次截取都需要浏览器重新渲染DOM,即重绘。重绘的影响还比较小,而如果截取的字符串行数发生改变,还会引发文本容器的高度变化,这时候就会导致浏览器回流,而文本容器在文档流中,回流将会影响整个文档。

        想解决这个问题,可以使用一个脱离文档流的元素来进行字符串动态截断后的渲染与判断,布局就类似上述的textarea。因为不在文档流中,回流的影响范围就会减少到该元素自身。获得截断长度后再截断文本,渲染到真正的文本容器即可。本文仅作为一个简单的原理概述的示例,没有做这个处理,对具体细节感兴趣的同学,可以查看github仓库代码。

查看原文

赞 31 收藏 22 评论 9

wenruo 赞了回答 · 2020-10-16

flex 布局 父元素padding-right与子元素最后一个元素 margin-right 失效有人遇到过么

我也碰到这个问题了,可以在wrapper 最后冗余一个div,并设置宽度 1px,感觉这样对dom结构改动比较小,应该比加容器那样方便点

关注 6 回答 3

wenruo 评论了文章 · 2019-03-02

JavaScript事件代理和委托

在JavaScript中,经常会碰到要监听列表中多项li的情形,假设我们有一个列表如下:


<ul id="list">
  <li id="item1">item1</li>
  <li id="item2">item2</li>
  <li id="item3">item3</li>
  <li id="item4">item4</li>
</ul>

如果我们要实现以下功能:当鼠标点击某一li时,alert输出该li的内容,我们通常的写法是这样的:

  • 当列表项比较少时,直接给每个li添加onclick事件
  • 列表项比较多时,在onload时就给每个列表项调用监听

第一种方法比较简单直接,但是没有顾及到html与JavaScript的分离,不建议使用,第二种方法的代码如下:


window.onload=function(){
  var ulNode=document.getElementById("list");
  var liNodes=ulNode.childNodes||ulNode.children;
  for(var i=0;i<liNodes.length;i++){
    liNodes[i].addEventListener('click',function(e){
      alert(e.target.innerHTML);
    },false);
  }
}

由上可以看出来,假如不停的删除或添加li,则function()也要不停的更改操作,易出错,因此推荐使用事件代理,在使用事件代理之前,我们先来了解一下事件阶段(event phase):

事件阶段:

当一个DOM事件被触发的时候,他并不是只在它的起源对象上触发一次,而是会经历三个不同的阶段。简而言之:事件一开始从文档的根节点流向目标对象(捕获阶段),然后在目标对向上被触发(目标阶段),之后再回溯到文档的根节点(冒泡阶段)如图所示(图片来自W3C):

事件捕获阶段(Capture Phase)

事件的第一个阶段是捕获阶段。事件从文档的根节点出发,随着DOM树的结构向事件的目标节点流去。途中经过各个层次的DOM节点,并在各节点上触发捕获事件,直到到达时间的目标节点。捕获阶段的主要任务是简历传播路径,在冒泡阶段,时间会通过这个路径回溯到文档根节点。


element.removeEventListener(&ltevent-name>, <callback>, <use-capture>);

我们通过上面的这个函数来给节点设置监听,可以通过将;设置成true来为事件的捕获阶段添加监听回调函数。在实际应用中,我们并没有太多使用捕获阶段监听的用例,但是通过在捕获阶段对事件的处理,我们可以阻止类似click事件在某个特定元素上被触发。


var form=document.querySeletor('form');
form.addEventListener('click',function(e){
  e.stopPropagation();
  },true);

如果你对这种用法不是很了解的话,最好还是将设置为false或者undefined,从而在冒泡阶段对事件进行监听。

目标阶段(Target Phase)

当事件到达目标节点时,事件就进入了目标阶段。事件在目标节点上被触发,然后逆向回流,知道传播到最外层的文档节点。

对于多层嵌套的节点,鼠标和指针事件经常会被定位到最里层的元素上。假设,你在一个div元素上设置了click的监听函数,而用户点击在了这个div元素内部的p元素上,那么p元素就是这个时间的目标元素。事件冒泡让我们可以在这个div或者更上层的元素上监听click事件,并且时间传播过程中触发回调函数。

冒泡阶段(Bubble Phase)

事件在目标事件上触发后,并不在这个元素上终止。它会随着DOM树一层层向上冒泡,直到到达最外层的根节点。也就是说,同一事件会一次在目标节点的父节点,父节点的父节点...直到最外层的节点上触发。

绝大多数事件是会冒泡的,但并非所有的。具体可见:规范说明

由上我们可以想到,可以使用事件代理来实现对每一个li的监听。代码如下:


window.onload=function(){
  var ulNode=document.getElementById("list");
  ulNode.addEventListener('click',function(e){
       if(e.target&&e.target.nodeName.toUpperCase()=="LI"){/*判断目标事件是否为li*/
         alert(e.target.innerHTML);
       }
     },false);
  
};
查看原文

wenruo 回答了问题 · 2019-01-24

element loading customClass怎么用?

element-loading-custom-class

关注 3 回答 1

wenruo 赞了文章 · 2018-09-13

想要清晰的明白(二)CSS 盒模型Block box与Line box

在上一篇想要清晰的明白(一): CSS视觉格式化模型|盒模型|定位方案|BFC比较宏观的了解了盒子模型的作用,接下来就详细的介绍两种盒子的具体细节

Block Box

图片描述
(图自《css权威指南》)

display : block 、 list-item 以及 table 会让一个元素成为块级元素。
 在Block Box中,会被水平格式化,垂直格式化,那我们就分垂直和水平来讲讲

水平格式化

如何计算其宽度

正常流中,块级元素框的水平部分 = 其父元素的width = margin-left+margin-right + padding-left+padding-right+ border-left+border-right+自身width

这个理解非常重要!是等于其父元素的width!

width & margin & auto

clipboard.png

clipboard.png

  1. 为什么.a中设置padding为auto,均invalid?
    在padding-left/right,margin-left/right,border-left/right,width(我们简称下水平7大属性)中只有margin和width的值可能为auto

  2. 设置margin-left为10px,显示正确,为什么margin-right是剩余的所有宽度?
    因为当margin-left/right,width三个值均设置有固定宽度的时候,margin-right会根据包含块的width自动补齐

  3. 利用margin:0 auto 居中
    所以,利用这种方式居中的时候,必须是要设置居中元素的宽度,这样左右margin的值便会相等,从而引起的居中,这个和text-align:center只能块级元素的内联内容设置居中是不一样的。

垂直格式化

height与width一样,height定义了内容区的高度,而不是元素框的高度。元素框上下的内边距,边距,都会增加到height值里。

只有三个属性可以设置auto,height,和margin-top/bottom。注意!这里如果margin-top和margin-bottom同时设置为auto,也不会垂直居中,而是默认为零

垂直格式化,有一个很重要的方面是会造成垂直相邻外边距合并,解决这个的方式见想要清晰的明白(一)中的BFC部分。

负margin

水平方向

问: 水平7大属性相加要等于父元素的width,那margin负值会造成什么?

若width不是固定值,那么width的值则会增大
因为要满足条件等于父元素width,负margin相当于负值,width auto自动增大
若width为固定值,那么margin-right则会auto增大来满足这个条件

clipboard.png

灰色部分是body内的一个盒子,图二,没有定框使用负margin后,发生偏移,并且宽度增加,图三,定宽,发生偏移但是,宽度不增加,我们常常会发现出现莫名的水平滚动条,这里很有可能就是margin这小子在作祟

垂直方向

clipboard.png

黑色是接在灰色div后的一个div,可以看到,margin-bottom为负值,不会造成元素本身的移动,而是造成兄弟元素往上移动,就像我不动,拉了下面的人一把,而margin-top为负值,就像我们排成一队,然后像兔子跳一样一起往前面跳了一步。

从图二也可以看出来,黑色盒子对灰色盒子发生了覆盖,因为浏览器总是按从前到后的顺序显示元素,所以文档中后出现的正常流元素可能会覆盖较早出现的元素。

可以发现,灰色盒子的高度依旧保持着并且渲染出来了,但是CSS读取的高度已经减小,下面的元素自然往上移动了

负margin与float应用 ——双飞翼


不知道为啥每次看到这名字,就....莫名的想笑,雅蠛蝶


clipboard.png

    <div class="main">
      <div class="main-container">身子</div>
    </div>
    <div class="left">左边小翅膀</div>
    <div class="right">右边小翅膀</div>

分析

margin负值导致两个翅膀是覆盖在div.main上,我们看到的蓝色部分是.main-container,margin-left:-100%,相当于是把第二排的第一个,从后面拉动一个100%距离,也就是到了第一排和第一排第一个一起站着,第二排第一个走了,第二排第二个(也就是div.right)自动补到他的位置上,这时候又来了个margin-left:-200px利用到第二排第二个上,往前拉动200px的距离,于是就到了现在div.right的位置

总而言之,水平上的七个元素的宽度和一定会等于父元素的宽度

Line Box

每一行称为一条Line Box,它又是由这一行的许多inline-box组成,它的高度可以直接由line-height决定,line boxes的高度垂直堆叠形成了containing box的高度,就是我们见到的div或是p标签之类的高度了。

基础概念

匿名文本

<div>当你只有一把锤子<span>一切看起来</span>都像是颗钉子</div>
未包含在行内元素的字符串(当你只有一把锤子,都像颗钉子)就叫匿名文本

内容区 行内框 间距

clipboard.png

内容区
css假设每个元素都会生成一个或者多个Box,称为元素框,元素框中心有内容区,内容区外周围包括了padding,border,margin,但是,替换元素是包括外边距,内边距,边框的。
行间距
行间距是font-size与line-height的差值,被分成两半在内容区的上下
行内框
非替换元素,行内框高度=line-height
替换元素,行内框高度=内容区宽度(行间距不应用到替换元素)
行高
两行文字基线的距离
行框
一行有很多行内框,行框是包含这一行行内框最高点和最低点的
基线
不同元素的基线位置不同,整个行框会有一个基线,行内元素的位置是基于两者基线对齐

vertical-align(垂直对齐)

该属性 定义 行内元素的基线相对于该元素所在行的基线的垂直对齐的方式。
只有一个元素属于inline或是inline-block(table-cell也可以理解为inline-block水平)水平,其身上的vertical-align属性才会起作用.
同时也可以知道,改变其,会影响到行内框的位置,从而会影响到一整行行内元素的位置

需要注意vertical-align为数值时,会让文字上下移动,当其为百分比时是针对font-size的百分比

line-height(行高)

<div style="width:100px;height:10px"></div>
//这个div调整line-height不会发生变化,因为里面没有文字
<span style="line-height:10px;border:1px solid green"></span> 
//span的高度会随着line-height的变化而变化
//说明行内元素的高度是由line-height的支撑决定,行内框的高度也等于line-height

管理line-height

因为line-height是根据自己font-size设置,而不是父元素,所以将line-height设置为1em,该元素的line-height则会与相同(em单位是一般是相对与父元素进行设置大小)

<div style="font-size:12px;line-height:12px">
    <span style="font-size:15px;line-height:1em">
        嘿嘿嘿,这里的line-height值为15px
    </span>
</div>

margin padding border对行高的影响

  • 行内元素其padding、margin、border-top、border-bottom 不会增加行高。
    padding会覆盖;margin将重置为0;border-top和border-bottom同样会覆盖。

clipboard.png

  • css2.1规定margin-top和margin-bottom可以运用到不是行内非替换元素的所有其他元素

  • 行内替换元素(如:img)元素会影响行高

inline-block

将对象呈现为inline对象,但是对象的内容作为block对象呈现。之后的内联对象会被排列在同一行内。比如我们可以给一个link(a元素)inline-block属性值,使其既具有block的宽度高度特性又具有inline的同行特性。

参考资料

《CSS 权威指南 第三版》
《The Definitive Guide to Using Negative Margins》
margin为负值产生的影响和常见布局应用
CSS布局 -- 圣杯布局 & 双飞翼布局
CSS深入理解vertical-align和line-height的基友关系
深入理解CSS中的行高

查看原文

赞 11 收藏 53 评论 7

wenruo 赞了回答 · 2018-09-02

解决phpstorm 识别.vue文件

找到问题了。我建立.vue后缀的文件时,选择了ts,所有phpstorm认为.vue的文件就是TypeScript类型的文件,使用TypeScript解析的方法去解释.vue文件,所以报错了。把 选项里面 Editor => File Types里找到Typescript,把你添加的.vue删掉(减号)。找到Vue.js 将.vue添加上。就把.vue文件当做vue.js解析了。就好了。。

关注 1 回答 1

认证与成就

  • 获得 3 次点赞
  • 获得 3 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 3 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-03-03
个人主页被 213 人浏览