position

position 指定一个元素在文档中的定位方式,使元素脱离当前的文档流,可以自由地在一定区域内移动。如果上级元素没有 relative,就以窗口作为定位范围,如果上级中有一个元素是 relative,就以它的范围作为最大的定位区域(子绝父相)。

display

display 设置块或者内联元素以及子元素的布局,例如 flex 布局、grid 布局。display 不会让元素脱离文档流,而是改变文档流中元素的排列方式。

float

float 指定一个元素应沿其父元素的左侧或右侧放置,允许该父元素内其他元素,如文本和内联元素环绕它。

简单示例

下面是使用了 float: left 的效果,最直观的是“编辑”和“回复”按钮在同一水平上:

image

下面是没有使用浮动属性的效果,“编辑”和“回复”不在同一水平上:

image

“编辑”和“回复”按钮不在同一个容器下,而是各自处于一个容器中。编辑和回复两个业务可以独立分开,所以编辑了两个组件,但因为按钮不在同一个容器中,就有 GIF2 中上下排序的效果:

image

要强制性让这两个不处于同一个容器里的按钮在同一个水平上,虽然 position 可以做到,但比较难处理定位,而且绝对定位会让元素不占有任何位置,好像是浮在上面的一层。float 虽然也脱离文档流,但是在它最终的位置上还是占据着位置:

image

单纯地脱离文档流还不行,需要清除浮动,不能让它有流动性,也就是不能让下一个元素围绕着上一个元素:

.clear::after {
  content: "";
  height: 0;
  clear: both;
  display: block;
  visibility: hidden;
}

在结束浮动的节点中添加上面的 clear 就可以取消浮动效果:

image

完整代码:

<style>
  .left {
    float: left;
  }

  .right {
    float: right;
  }

  .clear::after {
    content: "";
    height: 0;
    clear: both;
    display: block;
    visibility: hidden;
  }

  .mb-2 {
    margin-bottom: 10px;
  }
</style>
<body>
  <div id="app">
    <comment class="mb-2" v-for="(item, index) in listing" :key="index" :item="item" />
  </div>
</body>
<script>
  Vue.component("comment", {
    props: ["item"],
    template: `
      <div class="clear">
        <div class="text">{{ item.text }}</div>
        <div>
          <edit-comment :item='item' />
          <repl-comment :item='item' />
        </div>
      </div>
    `
  });

  Vue.component("edit-comment", {
    props: ["item"],
    methods: {
      edit() {
        this.item.isEdit = !this.item.isEdit;
      }
    },
    template: `
      <div class='left'>
        <textarea v-show="!item.isRepl && item.isEdit"></textarea>
        <button v-show="!item.isRepl && !item.isEdit" @click='edit'>编辑</button>
        <div v-show="!item.isRepl && item.isEdit">
          <button @click='item.isEdit = !item.isEdit'>取消编辑</button>
          <button @click='item.isEdit = !item.isEdit'>完成编辑</button>
        </div>
      </div>
    `
  });

  Vue.component("repl-comment", {
    props: ["item"],
    methods: {
      repl() {
        this.item.isRepl = !this.item.isRepl;
      }
    },
    template: `
      <div class='left'>
        <textarea v-show="item.isRepl && !item.isEdit"></textarea>
        <button v-show="!item.isRepl && !item.isEdit" @click='repl'>回复</button>
        <div v-show="item.isRepl && !item.isEdit">
          <button @click='item.isRepl = !item.isRepl'>取消回复</button>
          <button @click='item.isRepl = !item.isRepl'>完成回复</button>
        </div>
      </div>
    `
  });

  const app = new Vue({
    el: "#app",
    data() {
      return {
        listing: [
          {
            isRepl: false,
            isEdit: false,
            text: "Hello1"
          },
          {
            isRepl: false,
            isEdit: false,
            text: "Hello2"
          },
          {
            isRepl: false,
            isEdit: false,
            text: "Hello3"
          }
        ]
      };
    }
  });
</script>

示例的意义

示例中把回复和编辑写作组件,效果上差点意思。这样做的目的是使得两个再逻辑上独立,本来回复和编辑都要走不同的 API,在同一个组件里写代码会变多,这应该属于模块化思想,具体可以阅读:重新思考 Vue 组件的定义