教你利用Vue3模仿Windows窗口
一、前言
Vue3終于在2022年2月7日正式發(fā)布了,之前用vite+vue3搭了一個(gè)小demo,資料太少而我太菜了,所以一直不敢用Vue3搭新項(xiàng)目,現(xiàn)在隨著Vue3正式版本的發(fā)布,而且相關(guān)配合的子項(xiàng)目庫(kù)也已經(jīng)完善,大量的翻譯資料和文獻(xiàn)都已經(jīng)可以百度到了,再加上領(lǐng)導(dǎo)支持用Vue3新框架,所以我在新項(xiàng)目上著手用vue-cli(@vue/cli 4.5.9)腳手架搭建Vue3項(xiàng)目。
圖1 拖拽窗體效果展示
主要需求是做一個(gè)可以拖動(dòng)并且放大縮小的窗體,類似于系統(tǒng)桌面的窗口,功能是可拖動(dòng)然后寬高可通過鼠標(biāo)拖拽調(diào)整,查閱了大量的博文后,打算基于Vue的自定義指令directive來(lái)實(shí)現(xiàn),指令便于引用,而且使用的功能并不需要按照使用場(chǎng)景特殊化的修改,所以可以將這兩個(gè)功能封裝到指令中,然后基于這兩個(gè)指令(v-drag、v-resize)再去封裝一個(gè)通用窗體容器組件,項(xiàng)目框架基于Vue3+TS來(lái)實(shí)現(xiàn),由于TS是剛上手,所以基本any一把梭,希望各位大佬莫要嘲笑,不熟悉TS的同學(xué)也可以看著代碼實(shí)現(xiàn)一套JS版本的,主要功能都是JS基本功,和框架、語(yǔ)言的關(guān)系不大,只要能理解實(shí)現(xiàn)方法,簡(jiǎn)單的三劍客也能實(shí)現(xiàn)這個(gè)功能。接下來(lái)著手實(shí)現(xiàn)這個(gè)組件吧。
二、功能分析
圖2 dom對(duì)象屬性
Event對(duì)象屬性
因?yàn)槭峭蟿?dòng)和改變?cè)爻叽绱笮〉墓δ?,所以需要知道JS元素中的幾個(gè)屬性,如上圖所示,我們需要知道的如下所示:
- Dom對(duì)象屬性
- offsetTop: 返回當(dāng)前元素上邊界到其上級(jí)元素(offsetParent)的上邊界的距離【只讀】
- offsetLeft: 返回當(dāng)前元素左邊界到其上級(jí)元素(offsetParent)的左邊界的距離【只讀】
- offsetWidth: 返回元素的寬度,包含padding+border-width【只讀】
- offsetHeight: 返回元素的高度,包含padding+border-width 【只讀】
- clientWidth: 返回元素的寬度
- clientHeight: 返回元素的高度
- Event對(duì)象屬性
- offsetX: 相對(duì)于元素的橫坐標(biāo)
- offsetY: 相對(duì)于元素的縱坐標(biāo)
- clientX: 相對(duì)于瀏覽器窗口的橫坐標(biāo)
- clientY: 相對(duì)于瀏覽器窗口的縱坐標(biāo)
- pageX: 相對(duì)于頁(yè)面的橫坐標(biāo)
- pageY: 相對(duì)于頁(yè)面的縱坐標(biāo)
熟悉這幾個(gè)屬性后就可以著手來(lái)實(shí)現(xiàn)拖動(dòng)和尺寸調(diào)整了,主要實(shí)現(xiàn)思路如下:
- v-drag 將該指令掛載到第一個(gè)子元素,然后通過監(jiān)聽子元素的事件來(lái)實(shí)現(xiàn),通過子元素先獲取到父元素方便后續(xù)對(duì)其進(jìn)行操作,當(dāng)鼠標(biāo)按下事件觸發(fā)的時(shí)候開始對(duì)鼠標(biāo)移動(dòng)事件監(jiān)聽,按下的時(shí)候需要記錄鼠標(biāo)所在位置的x,y軸的坐標(biāo)值(相對(duì)于頁(yè)面的位置x,y),然后記錄拖動(dòng)前父元素的top,left的數(shù)值,再獲取窗口的寬高,減去父元素本身的寬高,計(jì)算得到父元素所能移動(dòng)的最大位移距離,超過距離不能再移動(dòng)。最后通過mousemove開始實(shí)時(shí)計(jì)算鼠標(biāo)位移距離,并將變化的位移距離更新到父元素,實(shí)現(xiàn)元素的移動(dòng)功能。
- v-resize 調(diào)整元素寬高的指令有一些復(fù)雜,需要給元素指定一個(gè)name屬性為resize,綁定該指令不能覆蓋預(yù)設(shè)的name值,然后通過name屬性確定是該元素。這里先定義一些需要記錄的屬性數(shù)據(jù),首先是cursor的屬性值,cursor是css中的指定鼠標(biāo)樣式的屬性,這里一共8個(gè)方位,所以分別列出這些屬性,并和top、bottom、left、right做一個(gè)關(guān)系映射,這樣方便理解,也容易操作。然后是記錄元素修改前的大小、位置、鼠標(biāo)按下的位置、改變方向,定義完這些變量后,對(duì)一些特殊的方法進(jìn)行聚合,首先是獲取鼠標(biāo)的方位,通過計(jì)算鼠標(biāo)在元素內(nèi)移動(dòng)的位置,設(shè)置一個(gè)內(nèi)邊距觸發(fā)計(jì)算方法,這里設(shè)置offset偏移量為12px,當(dāng)鼠標(biāo)在元素水平或垂直距離邊框?yàn)?2px的時(shí)候,就可以通過getDirection獲取到鼠標(biāo)所在的方位。再定義一個(gè)computedDistance 方法,用于計(jì)算鼠標(biāo)前后移動(dòng)的x,y的距離,最后就是計(jì)算改變尺寸方法的封裝,changeSize方法中獲取到鼠標(biāo)位移的距離,然后結(jié)合移動(dòng)的方向記錄值,進(jìn)行方法調(diào)用修改尺寸,方法中只將一半做了最小寬高設(shè)置,這里可以通過css來(lái)設(shè)置不用在js中編寫,后續(xù)組件封裝會(huì)看到。同樣觸發(fā)的方式是onmousedown的時(shí)候開啟事件,這里會(huì)獲取是否在8個(gè)方位范圍上,如果在就記錄按下按鈕時(shí)的數(shù)據(jù)和方位,并且觸發(fā)移動(dòng)計(jì)算方法,鼠標(biāo)按鈕抬起釋放的時(shí)候會(huì)對(duì)數(shù)據(jù)和方法重置,結(jié)束尺寸調(diào)整。 鼠標(biāo)樣式控制可以分開來(lái)看,主要對(duì)于寬高調(diào)整沒有影響,監(jiān)聽8個(gè)方位,然后修改鼠標(biāo)樣式,使交互操作更加友好。
三、指令封裝
v-drag與v-resize指令:
//directives.ts import { App } from "vue"; import { throttle } from "@/utils"; //節(jié)流函數(shù)不再展示,不要直接去除即可,在下面樣式引用去除即可 const directives = { drag: { mounted(el: any, binding: any, vnode: any) { // 如果傳遞了false就不啟用指令,反之true undefined null 不傳 則啟動(dòng) if (!binding.value && (binding.value ?? "") !== "") return; // 拖拽實(shí)現(xiàn) const odiv = el.parentNode; el.onmousedown = (eve: any) => { odiv.style.zIndex = 1; //當(dāng)前拖拽的在最前面顯示 eve = eve || window.event; const mx = eve.pageX; //鼠標(biāo)點(diǎn)擊時(shí)的坐標(biāo) const my = eve.pageY; //鼠標(biāo)點(diǎn)擊時(shí)的坐標(biāo) const dleft = odiv.offsetLeft; //窗口初始位置 const dtop = odiv.offsetTop; const clientWidth = document.documentElement.clientWidth; //頁(yè)面的寬 const oWidth = odiv.clientWidth; //窗口的寬 const maxX = clientWidth - oWidth; // x軸能移動(dòng)的最大距離 const clientHeight = document.documentElement.clientHeight; //頁(yè)面的高 const oHeight = odiv.clientHeight; //窗口的高度 const maxY = clientHeight - oHeight; //y軸能移動(dòng)的最大距離 document.onmousemove = (e: any) => { const x = e.pageX; const y = e.pageY; let left = x - mx + dleft; //移動(dòng)后的新位置 let top = y - my + dtop; //移動(dòng)后的新位置 if (left < 0) left = 0; if (left > maxX) left = maxX; if (top < 0) top = 0; if (top > maxY) top = maxY; odiv.style.left = left + "px"; odiv.style.top = top + "px"; odiv.style.marginLeft = 0; odiv.style.marginTop = 0; }; document.onmouseup = () => { document.onmousemove = null; }; }; } }, resize: { mounted(el: any, binding: any, vnode: any) { // 如果傳遞了false就不啟用指令,反之true undefined null 不傳 則啟動(dòng) if (!binding.value && (binding.value ?? "") !== "") return; // 給選定的元素綁定name屬性 設(shè)置name為resize區(qū)分只有該元素可以縮放 el.name = "resize"; // 八個(gè)方位對(duì)應(yīng) const mouseDir = { top: "n-resize", //上 bottom: "s-resize", //下 left: "w-resize", //左 right: "e-resize", //右 topright: "ne-resize", //右上 topleft: "nw-resize", //左上 bottomleft: "sw-resize", //左下 bottomright: "se-resize" //右下 }; // 記錄被修改元素的原始位置大小,以及變更方向 const pos = { width: 0, height: 0, top: 0, left: 0, x: 0, y: 0, dir: "" }; // 獲取鼠標(biāo)所在方位 const getDirection = (ev: any): string => { let dir = ""; const xP = ev.offsetX; const yP = ev.offsetY; const offset = 12; //內(nèi)邊距為多少時(shí)觸發(fā) // 計(jì)算是那個(gè)方位 if (yP < offset) dir += "top"; else if (yP > ev.toElement.clientHeight - offset) dir += "bottom"; if (xP < offset) dir += "left"; else if (xP > ev.toElement.clientWidth - offset) dir += "right"; return dir; }; // 計(jì)算移動(dòng)距離 const computedDistance = (pre: any, cur: any): any => { return [cur.x - pre.x, cur.y - pre.y]; }; //數(shù)據(jù)重置 const resetData = () => { pos.width = 0; pos.height = 0; pos.top = 0; pos.left = 0; pos.x = 0; pos.y = 0; pos.dir = ""; document.onmousemove = null; }; // 變更尺寸方法 const changeSize = (e: any) => { // 兩個(gè)點(diǎn)之間的差值,計(jì)算鼠標(biāo)位移數(shù)值 const [disX, disY] = computedDistance( { x: pos.x, y: pos.y }, { x: e.pageX, y: e.pageY } ); const addWid = pos.width + disX; const subWid = pos.width - disX; const addHig = pos.height + disY; const subHig = pos.height - disY; const minX = 200; const minY = 200; //上下左右的變更方法 const top = () => { if (subHig <= minY) return; //不能小于最小最高 el.style.height = subHig + "px"; el.style.top = pos.top + disY + "px"; }; // 上 const bottom = () => { el.style.height = addHig + "px"; }; // 下 const left = () => { if (subWid <= minX) return; //不能小于最小寬度 el.style.width = subWid + "px"; el.style.left = pos.left + disX + "px"; }; // 左 const right = () => { el.style.width = addWid + "px"; }; // 右 // 變更方位及其修改方法映射 const doFn = { top, //上 bottom, //下 left, //左 right, //右 topright: () => { top(); right(); }, //右上 topleft: () => { top(); left(); }, //左上 bottomleft: () => { bottom(); left(); }, //左下 bottomright: () => { bottom(); right(); } //右下 }; doFn[pos.dir](); }; //鼠標(biāo)按下 觸發(fā)變更事件 el.onmousedown = (e: any) => { if (e.target.name !== "resize") return; let d = getDirection(e); //當(dāng)位置為四個(gè)邊和四個(gè)角才開啟尺寸修改 if (mouseDir[d]) { pos.width = el.clientWidth; pos.height = el.clientHeight; pos.top = el.offsetTop; pos.left = el.offsetLeft; pos.x = e.pageX; pos.y = e.pageY; pos.dir = d; document.onmousemove = changeSize; } document.onmouseup = resetData; }; /** 鼠標(biāo)樣式變更 */ const changeShowCursor = throttle((e: any) => { e.preventDefault(); el.style.cursor = "default"; //先恢復(fù)鼠標(biāo)默認(rèn) if (e.target.name !== "resize") return; // 修改鼠標(biāo)顯示效果 let d = getDirection(e); // 確定是某個(gè)方位的動(dòng)向 el.style.cursor = mouseDir[d] || "default"; }, 200); //節(jié)流0.2s el.onmousemove = changeShowCursor; //監(jiān)聽根元素上移動(dòng)的鼠標(biāo)事件 } } }; export default (app: App) => { //批量注冊(cè)指令 Object.entries(directives).forEach(([key, fn]) => { app.directive(key, fn); }); };
上面的兩個(gè)指令,主要都是獲取元素本身,使用原生的js方法對(duì)元素進(jìn)行操作,需要注意的是v-drag是綁定在根元素的第一個(gè)子元素上(調(diào)整父元素的位置),而v-resize則是綁定元素本身(調(diào)整元素本身的大?。M瓿蓛蓚€(gè)指令的編寫后,可以在局部引用注冊(cè)或是全局注冊(cè),這里我使用全局注冊(cè)的方法。
//main.ts 全局注冊(cè) import { createApp } from "vue"; import App from "./App.vue"; import registerDirectives from "@/directives"; const app = createApp(App); registerDirectives(app); app.mount("#app");
全局注冊(cè)指令完成后,就可以在組件內(nèi)使用這兩個(gè)指令了,接下來(lái)我們編寫一個(gè)比較通用的彈窗組件,可以打開關(guān)閉,并且能夠拖動(dòng)和尺寸調(diào)整。
四、通用組件封裝
這里封裝組件的過程和Vue2差別不大,只是組件的編寫采用Vue3的組合式API寫法,其他方面基本都差不多,對(duì)于vue的css過渡效果2和3的版本有些許差異,這里請(qǐng)自行查閱Vue3文檔,剩下就是定義一些需要修改的屬性,使用props接收,并且設(shè)置默認(rèn)值,盡量讓組件可以更方便的自定義修改和擴(kuò)展。
下面是使用兩個(gè)指令后,封裝的一個(gè)彈窗組件,這里面在設(shè)置窗體css樣式drag-dialog的時(shí)候使用了min-width: 200px;min-height: 200px;max-width: 100vw;max-height: 100vh;
在這里通過對(duì)寬高的限制,就可以不用通過js來(lái)限制窗體的大小調(diào)整了,之前在寫v-resize指令的時(shí)候有提到過,使用js來(lái)控制顯示窗體的最小和最大顯示范圍,這里個(gè)人覺得還是通過css編寫方便一些。
<template> <transition name="drag-win"> <div class="drag-dialog ban-select-font" ref="dragWin" v-show="props.modelValue" v-resize="props.resizeAble" > <!-- 拖拽窗體頭部 --> <div class="drag-bar" :style="props.headStyle" v-drag="props.dragAble"> <slot name="head" /> <div class="drag-btn drag-close" @click="controlDialog" v-if="props.closeShow" /> <i class="drag-btn drag-full" @click="fullScreen" v-if="props.fullShow" /> </div> <!-- 拖拽框主要部分 --> <div class="drag-main" :style="props.mainStyle"> <slot /> </div> </div> </transition> </template> <script lang="ts" setup> import { ref } from "vue"; // props傳入數(shù)據(jù)類型約束 interface Props { modelValue: boolean; //控制窗體的顯示與否 width?: string; // 默認(rèn)寬 —— 設(shè)置頭高 寬高最好傳入變量 height?: string; // 默認(rèn)高 headHeight?: string; // 默認(rèn)控制欄高 headStyle?: string; // 控制欄樣式 mainStyle?: string; //主要內(nèi)容區(qū)域樣式 resizeAble?: boolean | string; // 是否可以調(diào)整尺寸 默認(rèn)可以調(diào)整 dragAble?: boolean | string; // 是否可以拖拽 默認(rèn)可拖拽 closeShow?: boolean; // 關(guān)閉控制顯示 默認(rèn)不顯示 fullShow?: boolean; // 全屏控制顯示 默認(rèn)不顯示 } /** 組件調(diào)整參數(shù)默認(rèn)值 */ const props = withDefaults(defineProps<Props>(), { modelValue: true, width: "500px", height: "60vh", headHeight: "35px", headStyle: "", mainStyle: "", resizeAble: "", dragAble: "", closeShow: false, fullShow: false }); // 窗體記錄數(shù)據(jù)類型約束 interface recordType { width: number; height: number; top: number; left: number; fill: boolean; } //記錄原來(lái)的大小 const recordBox: recordType = { width: 0, height: 0, top: 0, left: 0, fill: false }; //獲取窗口實(shí)體 const dragWin: any = ref(null); // 事件定義 const emits = defineEmits(["update:modelValue"]); /** 方法定義 */ // 內(nèi)部控制窗口開關(guān) const controlDialog = () => { emits("update:modelValue", !props.modelValue); }; // 全屏控件 const fullScreen = () => { const tmp = dragWin.value; const style = dragWin.value.style; // 寬的樣式 如果被手動(dòng)縮小或者放大,則表示非全屏狀態(tài),則將狀態(tài)置為false if (!style.width || style.width !== "100vw") { recordBox.fill = false; } // 全屏或是還原 if (recordBox.fill) { style.width = `${recordBox.width}px`; style.height = `${recordBox.height}px`; style.top = `${recordBox.top}px`; style.left = `${recordBox.left}px`; } else { // 記錄一下原來(lái)的樣式 recordBox.width = tmp.offsetWidth; recordBox.height = tmp.offsetHeight; recordBox.top = tmp.offsetTop; recordBox.left = tmp.offsetLeft; //全屏樣式 style.width = "100vw"; style.height = "100vh"; style.top = "0px"; style.left = "0px"; } recordBox.fill = !recordBox.fill; // 全屏狀態(tài)變換 }; </script> <style scoped> /* 禁止選中文字 */ .ban-select-font { -moz-user-select: none; /*火狐*/ -webkit-user-select: none; /*webkit瀏覽器*/ -ms-user-select: none; /*IE10*/ -khtml-user-select: none; /*早期瀏覽器*/ user-select: none; } .drag-dialog { position: fixed; width: v-bind("props.width"); height: v-bind("props.height"); left: calc(50% - v-bind("props.width") / 2); top: calc(50% - v-bind("props.height") / 2); box-sizing: border-box; padding: 8px; overflow: hidden; color: #fff; min-width: 200px; min-height: 200px; max-width: 100vw; max-height: 100vh; background-color: #313438cc; } .drag-bar { width: 100%; cursor: move; height: v-bind("props.headHeight"); border-bottom: 1px solid #fff; box-sizing: border-box; padding: 1px 2px 9px; } .drag-btn { width: 25px; height: 25px; float: right; cursor: pointer; margin-left: 5px; border-radius: 50%; } .drag-full { background-color: #28c940b8; } .drag-full:hover { background-color: #28c93f; } .drag-close { background-color: #f2473ec7; } .drag-close:hover { background-color: #f2473e; } .drag-main { width: 100%; height: calc(100% - v-bind("props.headHeight")); box-sizing: border-box; overflow: auto; font-size: 13px; line-height: 1.6; } /* vue漸入漸出樣式 */ .drag-win-enter-from, .drag-win-leave-to { opacity: 0; transform: scale(0); } .drag-win-enter-to, .drag-win-leave-from { opacity: 1; } .drag-win-enter-active, .drag-win-leave-active { transition: all 0.5s ease; } </style>
這個(gè)組件編寫還是有一些問題的,比如打開關(guān)閉的時(shí)候如果設(shè)置過top、left屬性,就會(huì)變回初始化時(shí)候定義的位置,這里可以參考放大縮小記錄一下窗口的位置等屬性,做一個(gè)關(guān)閉打開窗體的記錄,我這里沒有寫相關(guān)的代碼,主要是對(duì)我這個(gè)項(xiàng)目影響不大,所以有需要的同學(xué)可以自己嘗試一下怎么編寫(ps:主要還是懶)。
編寫完組件后就可以引用注冊(cè),可以全局或局部注冊(cè),這里我使用局部引用注冊(cè),然后編寫了兩個(gè)小例子,來(lái)使用封裝好的組件,可以查看組件封裝的props,通過里面的屬性來(lái)進(jìn)行組件定制化配置,增減所需功能,然后這里有兩個(gè)style,一個(gè)是頭部的樣式headStyle,一個(gè)是主體樣式mainStyle,最外層樣式直接在引用時(shí)編寫style調(diào)整即可,然后窗體寬高最好通過傳入字符串變量的方式,因?yàn)檫@里還涉及窗體所在容器內(nèi)的具體位置計(jì)算,默認(rèn)是水平垂直都居中。下面是引用代碼:
<template> <div>示例演示:</div> <button @click="control">{{ btnName }}</button> <button @click="box = !box">box控制</button> <IsDragDialog v-model="show" closeShow fullShow> <template #head>我是頭</template> <div>我是內(nèi)容區(qū)域</div> </IsDragDialog> <!-- 關(guān)閉某些選項(xiàng) --> <IsDragDialog style="top: 200px; left: 10px" v-model="box" :resize-able="false" drag-able closeShow fullShow width="100px" height="100px" /> </template> <script lang="ts" setup> import IsDragDialog from "@/components/IsDragDialog.vue"; //因?yàn)槭褂玫氖?script setup 這里組件會(huì)直接注冊(cè) import { computed } from "@vue/reactivity"; import { ref } from "vue"; const show = ref(true); const box = ref(true); const control = () => { show.value = !show.value; }; const btnName = computed(() => { return show.value ? `關(guān)閉窗口` : `打開窗口`; }); </script>
五、總結(jié)及其源代碼參考
功能實(shí)現(xiàn)主要還是對(duì)于dom元素自帶的屬性需要熟悉掌握,然后通過js的監(jiān)聽事件進(jìn)行組合事件觸發(fā),修改位置,調(diào)整dom元素的大小等等,通過一系列的變量參數(shù)修改與記錄,來(lái)實(shí)現(xiàn)拖動(dòng)和dom元素拖拽調(diào)整的功能。博文中的代碼可能還不夠全面,所以我將這個(gè)代碼抽離然后寫了個(gè)demo,基于vue/cli搭了個(gè)VUe3+TS的小例子,可以在gitee上下載,下面是源碼地址,npm i然后npm run sreve就可以查看組件demo了,其實(shí)這個(gè)組件還可以打包成npm包,但是精力有限,而且這個(gè)組件兼容性可能會(huì)有問題,所以等以后有機(jī)會(huì)再做個(gè)npm包吧。各位大佬,如果有什么更好的想法歡迎分享,也可以指出本文不足或錯(cuò)誤之處,歡迎指正批評(píng)。
源代碼地址:gitee.com/zero-dg/dra…
六、博文參考
http://www.dbjr.com.cn/article/245780.htm
到此這篇關(guān)于Vue3模仿Windows窗口的文章就介紹到這了,更多相關(guān)Vue3模仿Windows窗口內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用vue+element?ui實(shí)現(xiàn)走馬燈切換預(yù)覽表格數(shù)據(jù)
這次做項(xiàng)目的時(shí)候遇到需要切換預(yù)覽表格數(shù)據(jù)的需求,所以下面這篇文章主要給大家介紹了關(guān)于使用vue+element?ui實(shí)現(xiàn)走馬燈切換預(yù)覽表格數(shù)據(jù)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08如何用vue實(shí)現(xiàn)網(wǎng)頁(yè)截圖你知道嗎
這篇文章主要為大家介紹了vue如何實(shí)現(xiàn)網(wǎng)頁(yè)截圖,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2021-11-11vue3通過canvas實(shí)現(xiàn)圖片圈選功能
這篇文章將給大家詳細(xì)介紹了vue3如何通過canvas實(shí)現(xiàn)圖片圈選功能,文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴快來(lái)跟隨小編一起學(xué)習(xí)一下吧2023-12-12vue學(xué)習(xí)筆記之v-if和v-show的區(qū)別
本篇文章主要介紹了vue學(xué)習(xí)筆記之v-if和v-show的區(qū)別,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2017-09-09Vue實(shí)現(xiàn)Tab標(biāo)簽路由效果并用Animate.css做轉(zhuǎn)場(chǎng)動(dòng)畫效果的代碼
這篇文章主要介紹了Vue實(shí)現(xiàn)Tab標(biāo)簽路由效果,并用Animate.css做轉(zhuǎn)場(chǎng)動(dòng)畫效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07vue3.0?移動(dòng)端二次封裝van-uploader實(shí)現(xiàn)上傳圖片(vant組件庫(kù))
這篇文章主要介紹了vue3.0?移動(dòng)端二次封裝van-uploader上傳圖片組件,此功能最多上傳6張圖片,并可以實(shí)現(xiàn)本地預(yù)覽,實(shí)現(xiàn)代碼簡(jiǎn)單易懂,需要的朋友可以參考下2022-05-05詳解Vue-cli webpack移動(dòng)端自動(dòng)化構(gòu)建rem問題
這篇文章主要介紹了詳解Vue-cli webpack移動(dòng)端自動(dòng)化構(gòu)建rem問題,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2018-04-04關(guān)于Element-ui中Table表格無(wú)法顯示的問題及解決
這篇文章主要介紹了關(guān)于Element-ui中Table表格無(wú)法顯示的問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08一文帶你了解threejs在vue項(xiàng)目中的基本使用
three.js是一個(gè)用于在Web上創(chuàng)建三維圖形的JavaScript庫(kù),它可以用于創(chuàng)建各種類型的三維場(chǎng)景,包括游戲、虛擬現(xiàn)實(shí)、建筑和產(chǎn)品可視化等,下面這篇文章主要給大家介紹了關(guān)于如何通過一文帶你了解threejs在vue項(xiàng)目中的基本使用,需要的朋友可以參考下2023-04-04