10x2 精读Vue官方文档 - 示例 - 内嵌组件 & 弹力拖动

787 阅读2分钟

精读 Vue 官方文档系列 🎉


内嵌组件

使用 Vue 兼容第三方生态的范例。例如基于 JavaScript 或 Jquery 的插件、widget、库等。

一些诀窍:

  • 选用的插件、widget 最好自带样式作用域隔离,比如基于 BEM
  • 需要依赖 DOM 元素的,应该将插件的执行时期延缓到 mounted 生命周期内,此时可以通过 this.$el 获取组件的根元素。
  • 可以将插件的事件系统结合到 Vue 的程序化事件监听器中共同使用。
  • 组件销毁时 destored 要主动去消耗插件的实例,防止内存泄漏。

代码示例:

mounted: function () {
    var vm = this;
    $(this.$el)
        // init select2
        .select2({ data: this.options })
        .val(this.value)
        .trigger("change")
        // emit event on change.
        .on("change", function () {
            //触发 v-model,将插件的事件转换为 Vue 的事件。
            vm.$emit("input", this.value);
        });
},
watch: {
    value: function (value) {
        // update value
        $(this.$el).val(value).trigger("change");
    },
    options: function (options) {
        // update options
        $(this.$el).empty().select2({ data: options });
    }
},
destroyed: function () {
    //销毁组件
    $(this.$el).off().select2("destroy");
}

具有伸缩性的 Header Example

<template>
  <div
    class="card"
    @mousedown="startDrag"
    @mousemove="onDrag"
    @mouseup="stopDrag"
    @mouseleave="stopDrag"
  >
    <div class="card__header" :style="computedStyle"> </div>
    <div class="card__body"> </div>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      dragging: false, //是否按下,准备拖动
      start: { x: 0, y: 0 }, //记录起点坐标
      distance: { x: 0, y: 0 }, //记录拖动的坐标
    };
  },
  computed: {
    computedStyle() {
      const dy = this.distance.y;
      const dampen = dy > 0 ? 7 : 12;
      const height = 160 + dy / dampen; //160px 是固定的头部高度,所以要加上去。
      const radius = dy / 480 * 100;
      return { height: height + "px", borderBottomLeftRadius: radius + "%",borderBottomRightRadius: radius + "%" };
    },
  },
  methods: {
    startDrag(e) {
      e = e.changeTouches ? e.changeTouches : e;
      this.dragging = true;
      this.start.x = e.pageX;
      this.start.y = e.pageY;
    },
    onDrag(e) {
      e = e.changeTouches ? e.changeTouches : e;
      if (this.dragging) {
        this.distance.y = e.pageY - this.start.y;
      }
    },
    stopDrag() {
      if (this.dragging) {
        this.dragging = false;
        window.dynamics.animate(
          this.distance,
          {
            x: 0,
            y: 0,
          },
          {
            type: window.dynamics.spring,
            duration: 700,
            friction: 280,
          }
        );
      }
    },
  },
};
</script>
<style scoped>
.card {
  width: 320px;
  height: 480px;
  background: #eee;
  margin: 500px auto;
  user-select: none;
}
.card__header {
  height: 160px;
  background: grey;
  box-sizing: border-box;
  padding: 30px;
  transition: all .1s;
}
.card__body {
  box-sizing: border-box;
  padding: 30px;
}
</style>
  1. 我们需要通过 start 来记录用户每次拖动时的起始坐标,通过拖动坐标减去起始坐标,得到的才是有效的拖动距离。
  2. 通过添加 draging 标志结合 mousemove 事件,用来判断用户是否已经准备拖动以及是否已处于拖动中。
  3. 手动调参给出一个大致效果的阻尼值 dampen,并且通过判断的 distance.y 值的正负来应用不同的阻尼系数。
  4. mouseleave 事件不支持冒泡,所以进入其子元素内部不会触发离开事件。
  5. 使用 dynamics 动画库实现物理动画。

最后

其实我对官方示例中的“阻尼”计算方式很困惑,官方中会在拖动过程中用实际的拖动距离除以一个阻尼值。


onDrag: function(e) {
    e = e.changedTouches ? e.changedTouches[0] : e;
    if (this.dragging) {
      // dampen vertical drag by a factor
      var dy = e.pageY - this.start.y;
      var dampen = dy > 0 ? 1.5 : 4;
      this.c.y = 160 + dy / dampen;
    }
}

但是在计算属性中修改样式时又计算了一次阻尼。

{
    computed:{
         contentPosition: function() {
            var dy = this.c.y - 160;
            var dampen = dy > 0 ? 2 : 4;
            return {
              transform: "translate3d(0," + dy / dampen + "px,0)"
            };
          }
    }
}

看上去应该只需要计算一次即可,没必要把拖动距离的计算分在两个地方,分别除以两个不同的阻尼系数,如果你能理解官方示例的真正含义,请麻烦在下面给我评论,感激不尽。

官方示例地址:cn.vuejs.org/v2/examples…