原文:http://gambardella.info/2016/09/05/guide-how-to-use-vue-js-with-jquery-plugins
使用Vue真的太棒了,但是也有可能使你頭疼,當你試圖使它與jquery插件混用的時候。
問題的原因是jquery與Vue是完全不同的東西,Vue是通過組件與數據綁定來進行渲染的,jquery則主要是用來做簡單的單擊處理和強大的操縱DOM的能力。
我試圖尋找一些東西來幫助自己解決這個問題,但是我發現一些組件所做的工作不是很讓我滿意,所以讓我告訴你怎樣使Vue與jquery插件混用。
這么做的目的是什么?
大多數情況下你能使用jquery和一些簡單的Vue來滿足你的需求,Modals, sliders等等和一些Vue組件結合起來是很快的。
所以我們的目標是用強大的jQuery插件與Vue快速結合。
我們將...
- 用Vue的自定義指令來構建jquery
- 當元素生成時初始化插件
- 當元素撤銷時銷毀插件
- 發送事件來通知組件
- 從組件接收事件並將它們傳遞給插件
教程時間
我選擇Fengyuan Chen’s的cropper因為它是一個很棒的jQuery插件,如果時間限制在60分鍾之內的話你肯定不可能重寫這個插件在不使用Vue的情況下。
DEMO:https://vue-jquery-cropper-demo.firebaseapp.com/
我將從最開始來演示,如果你已經完成請跳過這個部分。
創建項目
# install vue-cli $ npm install -g vue-cli # create a new project using the "webpack" boilerplate $ vue init webpack vue-cropper ? Project name "vue-cropper" ? Project description "A Vue.js project" ? Author "Christian Gambardella <christian@gambardella.info>" ? Use ESLint to lint your code? "Yes" ? Pick an ESLint preset "Standard" ? Setup unit tests with Karma + Mocha? "No" ? Setup e2e tests with Nightwatch? "No" $ cd vue-cropper $ npm install
祝賀你已經有了一個Vue的項目。
安裝 jQuery 和 cropper.js
# install jQuery & cropper $ npm install jquery cropper --save
為jquery和Vue自定義指令配置webpack
為webpack配置添加jquery和Vue自定義指令的映射。
通常webpack已經引入了完整的jquery版本,但還是建議再一次引入一下。
您可以看到Vue的webpack模板已經添加到組件的文件夾中。我通常會添加很多其他文件夾像自定義指令,mixin等等。在這個例子中,我們只添加了自定義指令。
這將幫助我們引入依賴關系而無需知道其確切的路徑。這也是有益的在你重構你的應用的時候。你也並不需要管理相對路徑。
把下面高亮部分添加到build/webpack.base.conf文件中。
resolve: { extensions: ['', '.js', '.vue'], fallback: [path.join(__dirname, '../node_modules')], alias: { 'src': path.resolve(__dirname, '../src'), 'assets': path.resolve(__dirname, '../src/assets'), 'components': path.resolve(__dirname, '../src/components'), 'jquery': path.resolve(__dirname, '../node_modules/jquery/src/jquery'), 'directives': path.resolve(__dirname, '../src/directives') } },
不要忘了在末尾添加逗號。
准備應用組件
我要開始使用組件,因為我相信這是更容易理解的,漂亮和簡單的組件。如果你已經知道如何在組件中使用它們那么就可以立即寫這個指令來實現它們。
替換src/App.vue文件的template部分
<template> <div id="app"> <img v-cropper="cropOptions" src="https://i.imgur.com/WcvkCxl.png" alt="jQuery Meme"> </div> </template>
替換src/App.vue文件的script部分
<script> import Cropper from './directives/Cropper' export default { directives: { Cropper }, data () { return { cropOptions: { viewMode: 0, zoomable: false } } } } </script>
一些需要重點注意的地方
- 我們引入jQuery也不需要處理組件的初始化。
- 把Cropper的設置選項當做原始綁定數據。
- Cropper將在初始化時渲染,再銷毀時使其消失。
- 組件的名字可以在template用作標簽使用,MyCropper轉變為my-cropper。
- 對它進行初始化時,我們必須添加v-my-cropper元素在組件的模板里。
創建Cropper組件
這是本教程的核心。我們要創建一個Cropper指令來處理低層代碼的DOM操作。在這種情況下我們要初始化Cropper。
自定義指令是用來提供一種機制來映射數據並任意操作DOM的行為。
創建src/directives/Cropper.js
import jQuery from 'jquery' import 'cropper' export default { deep: true, bind () {}, update (options) { // Destroy in case it has been initialized already. jQuery(this.el).cropper('destroy') // Initializing directly after destroying // didn't work. Wrapping it in a setTimeout // seems to do the trick. setTimeout(() => { jQuery(this.el).cropper(options) }, 0) }, unbind () { jQuery(this.el).cropper('destroy') } }
每一個Vue自定義組件的最核心部分是其提供可以用來綁定,更新,解綁相應方法。
- bind:當元素在文檔中生成時只觸發一次。
- update:當bind方法觸發后和綁定數據改變時觸發,在這種情況下cropOptions只是一個對象。Vue.js自定義指令需要知道如果它的相關屬性也是一個對象。這就是為什么我們需要添加deep: true在這種情況下。
- unbind:當DOM元素被移除時觸發,在這個方法里可以銷毀組件並且移除監聽的事件。
在Vue 2中會有些許變化。
vm實例被作為函數參數傳遞,不會被設置為this。
指令的update方法只能在數據改變后觸發而不會在bind方法之后觸發。
第一次運行
這是我們所能做的最小限度,讓我們運行起來看看如何。
這是很快做出來的,所以我們並沒有添加cropper的樣式,讓我們添加並再一次運行。
先添加高亮部分在src/main.js文件中
import '../node_modules/cropper/dist/cropper.css' import Vue from 'vue' import App from './App' /* eslint-disable no-new */ new Vue({ el: 'body', components: { App } })
啟動服務
$ npm run dev
當所有事情都做好后,你應該看到一個圖片和一個cropbox並且可以使其移動。
切換 on/off
從現在起一切都會很簡單。我們將通過這個案件。使每件事都會簡單起來。
我們用一個按鈕來啟用和禁用cropper。頁面開始用戶選擇一個文件切換為cropper的打開狀態。證明了cropper可以在任何時候被初始化。
編輯src/App.vue的script部分
import Cropper from './directives/Cropper' export default { directives: { Cropper }, data () { return { cropOptions: { viewMode: 0, zoomable: false }, showCropper: false } }, methods: { toggleCropper () { this.showCropper = !this.showCropper } } }
- data中添加showCropper: false
- 添加一個切換方法toggleCropper
編輯src/App.vue的template部分
<div id="app"> <button id="toggle" @click="toggleCropper">Toggle Cropper</button> <img v-if="showCropper" v-cropper="cropOptions" src="https://i.imgur.com/WcvkCxl.png" alt="Mustache"> </div>
- 添加一個按鈕綁定@click
- 在img標簽上添加v-if指令
每一次圖像的顯示都會調用自定義cropper指令的bind和update方法。
當showCropper設置為false時,cropper的unbind將被調用。
這里不會使我們陷入困境。
cropper將被加載在用戶需要時和卸載后。
改變選項
我們已經擁有需要更新cropper的選項。如果我們有一個現有jQuery插件,並且會接收新選項,我們將不需要銷毀當前實例來重新創建插件實例。不幸的是cropper不允許這樣。
讓我們添加一個開關來控制圖像縮放。
編輯src/App.vue的template部分
<div id="app"> <button id="toggle" @click="toggleCropper">Toggle Cropper</button> <img v-if="showCropper" v-cropper="cropOptions" src="https://i.imgur.com/WcvkCxl.png" alt="Mustache"> <input id="zoomable" type="checkbox" v-model="cropOptions.zoomable"> <label for="zoomable">Zoomable?</label> </div>
很簡單,對嗎?
通過指令獲取數據
cropper有一個回調方法,當我們觸發一個事件時將會調用這個方法。
編輯src/directives/Cropper.js
import jQuery from 'jquery' import 'cropper' export default { deep: true, bind () {}, update (options) { options.crop = (event) => { this.vm.$emit('crop', event) } // Destroy in case it has been initialized already. jQuery(this.el).cropper('destroy') // Initializing directly after destroying // didn't work. Wrapping it in a setTimeout // seems to do the trick. setTimeout(() => { jQuery(this.el).cropper(options) }, 0) }, unbind () { jQuery(this.el).cropper('destroy') } }
編輯App組件的script部分來處理crop事件
import Cropper from './directives/Cropper' export default { directives: { Cropper }, data () { return { cropData: {}, cropOptions: { viewMode: 0, zoomable: false }, showCropper: false } }, events: { crop (event) { this.cropData = { x: event.x, y: event.y, width: event.width, height: event.height } } }, methods: { toggleCropper () { this.showCropper = !this.showCropper } } }
編輯App組件的template部分來展示crop數據
<template> <div id="app"> <button id="toggle" @click="toggleCropper">Toggle Cropper</button> <img v-if="showCropper" v-cropper="cropOptions" src="https://i.imgur.com/WcvkCxl.png" alt="Mustache"> <input id="zoomable" type="checkbox" v-model="cropOptions.zoomable"> <label for="zoomable">Zoomable?</label> <pre id="output"> x: {{ cropData.x }} y: {{ cropData.y }} width: {{ cropData.width }} height: {{ cropData.height }} </pre> </div> </template>
正如您所看到的我選擇從指令來發出事件來監聽App組件。問問自己為什么選擇這么做。我也可以選擇在App組件上面創建一個方法,將其綁定到cropper的監聽器中。
反模式。options.crop = this.vm.handleCrop
操作其他action
最后使事件從一個組件流向一個指令來改變顯示狀態。我們要用它來調用cropper的action。讓我告訴你我將如何添加一個按鈕來控制圖片旋轉。
編輯App組件的temlpate部分並添加一個控制圖片旋轉的按鈕
<template> <div id="app"> <button id="toggle" @click="toggleCropper">Toggle Cropper</button> <img v-if="showCropper" v-cropper="cropOptions" src="https://i.imgur.com/WcvkCxl.png" alt="Mustache"> <input id="zoomable" type="checkbox" v-model="cropOptions.zoomable"> <label for="zoomable">Zoomable?</label> <button @click="$emit('rotate', 90)">Rotate 90°</button> <pre id="output"> x: {{ cropData.x }} y: {{ cropData.y }} width: {{ cropData.width }} height: {{ cropData.height }} </pre> </div> </template>
編輯src/directives/Cropper.js添加用來監聽和解綁的事件
import jQuery from 'jquery' import 'cropper' export default { deep: true, bind () { this.vm.$on('rotate', (deg) => { jQuery(this.el).cropper('rotate', deg) }) }, update (options) { options.crop = (event) => { this.vm.$emit('crop', event) } // Destroy in case it has been initialized already. jQuery(this.el).cropper('destroy') // Initializing directly after destroying // didn't work. Wrapping it in a setTimeout // seems to do the trick. setTimeout(() => { jQuery(this.el).cropper(options) }, 0) }, unbind () { jQuery(this.el).cropper('destroy') this.vm.$off('rotate') } }
摘要
Et voilà you’re done!
這樣做的美妙之處在於,指令是用來解耦組件並在多個組件中使用,它會自動初始化並附加到DOM元素中,在DOM被移除之前銷毀。
我希望你會喜歡這篇文章,如果你有問題和建議,可隨時給我發消息或評論文章。
Best,
Christian