1. 程式人生 > 遊戲 >哥特式吸血鬼遊戲V Rising搶先發布

哥特式吸血鬼遊戲V Rising搶先發布

一、 什麼是自定義指令

我們看到的v-開頭的行內屬性,都是指令,不同的指令可以完成或實現不同的功能,對普通 DOM元素進行底層操作,這時候就會用到自定義指令。除了核心功能預設內建的指令 (v-model 和 v-show),Vue 也允許註冊自定義指令

指令使用的幾種方式:

//會例項化一個指令,但這個指令沒有引數
v-xxx

// -- 將值傳到指令中
v-xxx="value"

// -- 將字串傳入到指令中,如v-html="'<p>內容</p>'"
v-xxx="'string'"

// -- 傳引數(arg),如v-bind:class="className"
v-xxx:arg="value"

// -- 使用修飾符(modifier
v-xxx:arg.modifier="value"

二、 如何自定義指令

註冊一個自定義指令有全域性註冊與區域性註冊

全域性註冊註冊主要是用過Vue.directive方法進行註冊

Vue.directive第一個引數是指令的名字(不需要寫上v-字首),第二個引數可以是物件資料,也可以是一個指令函式

// 註冊一個全域性自定義指令 `v-focus`
Vue.directive('focus', {
  // 當被繫結的元素插入到 DOM 中時……
  inserted: function (el) {
    // 聚焦元素
    el.focus()  // 頁面載入完成之後自動讓輸入框獲取到焦點的小功能
  }
})

區域性註冊通過在元件options選項中設定directive屬性

directives: {
  focus: {
    // 指令的定義
    inserted: function (el) {
      el.focus() // 頁面載入完成之後自動讓輸入框獲取到焦點的小功能
    }
  }
}

然後你可以在模板中任何元素上使用新的 v-focus property,如下:

<input v-focus />

鉤子函式
自定義指令也像元件那樣存在鉤子函式:

bind:只調用一次,指令第一次繫結到元素時呼叫。在這裡可以進行一次性的初始化設定
inserted:被繫結元素插入父節點時呼叫 (僅保證父節點存在,但不一定已被插入文件中)
update:所在元件的 VNode 更新時呼叫,但是可能發生在其子 VNode更新之前。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前後的值來忽略不必要的模板更新
componentUpdated:指令所在元件的 VNode 及其子 VNode 全部更新後呼叫
unbind:只調用一次,指令與元素解綁時呼叫
所有的鉤子函式的引數都有以下:
el:指令所繫結的元素,可以用來直接操作 DOM
binding:一個物件,包含以下 property:

name:指令名,不包括 v- 字首。

value:指令的繫結值,例如:v-my-directive="1 + 1" 中,繫結值為 2。

oldValue:指令繫結的前一個值,僅在 update 和 componentUpdated 鉤子中可用。無論值是否改變都可用。

expression:字串形式的指令表示式。例如 v-my-directive="1 + 1" 中,表示式為 "1 + 1"。

arg:傳給指令的引數,可選。例如 v-my-directive:foo 中,引數為 "foo"。

modifiers:一個包含修飾符的物件。例如:v-my-directive.foo.bar 中,修飾符物件為 { foo: true, bar: true }

vnode:Vue 編譯生成的虛擬節點

oldVnode:上一個虛擬節點,僅在 update 和 componentUpdated 鉤子中可用

除了 el 之外,其它引數都應該是隻讀的,切勿進行修改。如果需要在鉤子之間共享資料,建議通過元素的 dataset 來進行

<div v-demo="{ color: 'white', text: 'hello!' }"></div>
<script>
    Vue.directive('demo', function (el, binding) {
    console.log(binding.value.color) // "white"
    console.log(binding.value.text)  // "hello!"
    })
</script>

三、應用場景

使用自定義元件元件可以滿足我們日常一些場景,這裡給出幾個自定義元件的案例:

防抖
圖片懶載入
一鍵 Copy的功能

輸入框防抖
防抖這種情況設定一個v-throttle自定義指令來實現

// 1.設定v-throttle自定義指令
Vue.directive('throttle', {
  bind: (el, binding) => {
    let throttleTime = binding.value; // 防抖時間
    if (!throttleTime) { // 使用者若不設定防抖時間,則預設2s
      throttleTime = 2000;
    }
    let cbFun;
    el.addEventListener('click', event => {
      if (!cbFun) { // 第一次執行
        cbFun = setTimeout(() => {
          cbFun = null;
        }, throttleTime);
      } else {
        event && event.stopImmediatePropagation();
      }
    }, true);
  },
});
// 2.為button標籤設定v-throttle自定義指令
<button @click="sayHello" v-throttle>提交</button>

圖片懶載入
設定一個v-lazy自定義元件完成圖片懶載入

const LazyLoad = {
    // install方法
    install(Vue,options){
       // 代替圖片的loading圖
        let defaultSrc = options.default;
        Vue.directive('lazy',{
            bind(el,binding){
                LazyLoad.init(el,binding.value,defaultSrc);
            },
            inserted(el){
                // 相容處理
                if('InterpObserver' in window){
                    LazyLoad.observe(el);
                }else{
                    LazyLoad.listenerScroll(el);
                }
            },
        })
    },
    // 初始化
    init(el,val,def){
        // src 儲存真實src
        el.setAttribute('src',val);
        // 設定src為loading圖
        el.setAttribute('src',def);
    },
    // 利用InterpObserver監聽el
    observe(el){
        let io = new InterpObserver(entries => {
            let realSrc = el.dataset.src;
            if(entries[0].isIntersecting){
                if(realSrc){
                    el.src = realSrc;
                    el.removeAttribute('src');
                }
            }
        });
        io.observe(el);
    },
    // 監聽scroll事件
    listenerScroll(el){
        let handler = LazyLoad.throttle(LazyLoad.load,300);
        LazyLoad.load(el);
        window.addEventListener('scroll',() => {
            handler(el);
        });
    },
    // 載入真實圖片
    load(el){
        let windowHeight = document.documentElement.clientHeight
        let elTop = el.getBoundingClientRect().top;
        let elBtm = el.getBoundingClientRect().bottom;
        let realSrc = el.dataset.src;
        if(elTop - windowHeight<0&&elBtm > 0){
            if(realSrc){
                el.src = realSrc;
                el.removeAttribute('src');
            }
        }
    },
    // 節流
    throttle(fn,delay){
        let timer; 
        let prevTime;
        return function(...args){
            let currTime = Date.now();
            let context = this;
            if(!prevTime) prevTime = currTime;
            clearTimeout(timer);
            if(currTime - prevTime > delay){
                prevTime = currTime;
                fn.apply(context,args);
                clearTimeout(timer);
                return;
            }
            timer = setTimeout(function(){
                prevTime = Date.now();
                timer = null;
                fn.apply(context,args);
            },delay);
        }
    }
}
export default LazyLoad;

一鍵 Copy的功能

import { Message } from 'ant-design-vue';
const vCopy = { //
  /*
    bind 鉤子函式,第一次繫結時呼叫,可以在這裡做初始化設定
    el: 作用的 dom 物件
    value: 傳給指令的值,也就是我們要 copy 的值
  */
  bind(el, { value }) {
    el.$value = value; // 用一個全域性屬性來存傳進來的值,因為這個值在別的鉤子函式裡還會用到
    el.handler = () => {
      if (!el.$value) {
      // 值為空的時候,給出提示,我這裡的提示是用的 ant-design-vue 的提示,你們隨意
        Message.warning('無複製內容');
        return;
      }
      // 動態建立 textarea 標籤
      const textarea = document.createElement('textarea');
      // 將該 textarea 設為 readonly 防止 iOS 下自動喚起鍵盤,同時將 textarea 移出可視區域
      textarea.readOnly = 'readonly';
      textarea.style.position = 'absolute';
      textarea.style.left = '-9999px';
      // 將要 copy 的值賦給 textarea 標籤的 value 屬性
      textarea.value = el.$value;
      // 將 textarea 插入到 body 中
      document.body.appendChild(textarea);
      // 選中值並複製
      textarea.select();
      // textarea.setSelectionRange(0, textarea.value.length);
      const result = document.execCommand('Copy');
      if (result) {
        Message.success('複製成功');
      }
      document.body.removeChild(textarea);
    };
    // 繫結點選事件,就是所謂的一鍵 copy 啦
    el.addEventListener('click', el.handler);
  },
  // 當傳進來的值更新的時候觸發
  componentUpdated(el, { value }) {
    el.$value = value;
  },
  // 指令與元素解綁的時候,移除事件繫結
  unbind(el) {
    el.removeEventListener('click', el.handler);
  },
};
export default vCopy;

拖拽

<div ref="a" id="bg" v-drag></div>

  directives: {
    drag: {
      bind() {},
      inserted(el) {
        el.onmousedown = (e) => {
          let x = e.clientX - el.offsetLeft;
          let y = e.clientY - el.offsetTop;
          document.onmousemove = (e) => {
            let xx = e.clientX - x + "px";
            let yy = e.clientY - y + "px";
            el.style.left = xx;
            el.style.top = yy;
          };
          el.onmouseup = (e) => {
            document.onmousemove = null;
          };
        };
      },
    },
  }

vue自定義指令的應用場景
使用自定義指令背景
程式碼複用和抽象的主要形式是元件。
當需要對普通 DOM 元素進行底層操作,此時就會用到自定義指令
但是,對於大幅度的 DOM 變動,還是應該使用元件
常用案例
1、 輸入框自動聚焦

// 註冊一個全域性自定義指令 `v-focus`
Vue.directive('focus', {
  // 當被繫結的元素插入到 DOM 中時
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})
//<input v-focus>

2、下拉選單
點選下拉選單本身不會隱藏選單
點選下拉選單以外的區域隱藏選單

<script>
Vue.directive('clickoutside', {
  bind(el, binding) {
    function documentHandler(e) {
      if (el.contains(e.target)) {
       return false 
      }
      if (binding.expression) {
        binding.value(e)
      }
    }
    el.__vueMenuHandler__ = documentHandler
    document.addEventListener('click', el.__vueMenuHandler__)
  },
  unbind(el) {
    document.removeEventListener('click', el.__vueMenuHandler__)
    delete el.__vueMenuHandler__
  }
})

new Vue({
  el: '#app',
  data: {
    show: false
  },
  methods: {
    handleHide() {
      this.show = false
    }
  }
})
</script>
<div class="main" v-menu="handleHide">
  <button @click="show = !show">點選顯示下拉選單</button>
  <div class="dropdown" v-show="show">
    <div class="item"><a href="#">選項 1</a></div>
    <div class="item"><a href="#">選項 2</a></div>
    <div class="item"><a href="#">選項 3</a></div>
  </div>
</div>

3、相對時間轉換
類似微博、朋友圈釋出動態後的相對時間,比如剛剛、兩分鐘前等等

<span v-relativeTime="time"></span>
<script>
new Vue({
  el: '#app',
  data: {
    time: 1565753400000
  }
})

Vue.directive('relativeTime', {
  bind(el, binding) {
    // Time.getFormatTime() 方法,自行補充
    el.innerHTML = Time.getFormatTime(binding.value)
    el.__timeout__ = setInterval(() => {
      el.innerHTML = Time.getFormatTime(binding.value)
    }, 6000)
  },
  unbind(el) {
    clearInterval(el.innerHTML)
    delete el.__timeout__
  }
})
</script>

理論:
vue中的自定義指令:
vue中除了核心功能內建的指令外,也允許註冊自定義指令。自定義指令又分為全域性的自定義指令和區域性自定義指令。
全域性自定義指令是通過Vue.directive(‘第一個引數是指令的名稱’,{第二個引數是一個物件,這個物件上有鉤子函式})

Vue.directive('focus', {
		// el:指令所繫結的元素,可以用來直接操作 DOM。
		//binding:一個物件,包含以下 property:
      inserted: function (el) { vNode引數
        el.focus();
      }
    });

區域性自定義指令:
是定義在元件內部的,只能在當前元件中使用

directives: {
	// 指令名稱
	dir1: {
		inserted(el) {  / inserted 表示被繫結元素插入父節點時呼叫
		// 指令中第一個引數是當前使用指令的DOM
			console.log(el);
			console.log(arguments);
			// 對DOM進行操作
			el.style.width = '200px';
			el.style.height = '200px';
			el.style.background = '#000';
		}
	},
	color: { // 為元素設定指定的字型顏色
		bind(el, binding) {
		el.style.color = binding.value;
		}
	}
}

鉤子函式:

一個指令定義物件可以提供如下幾個鉤子函式 (均為可選):
inserted:被繫結元素插入父節點時呼叫 (僅保證父節點存在,但不一定已被插入文件中)。
bind:只調用一次,指令第一次繫結到元素時呼叫。
update:所在元件的 VNode 更新時呼叫,但是可能發生在其子 VNode 更新之前
componentUpdated:指令所在元件的 VNode 及其子 VNode 全部更新後呼叫。
unbind:只調用一次,指令與元素解綁時呼叫。
————————————————
原文連結:https://blog.csdn.net/weixin_58032613/article/details/122759818