乐趣区

关于html5:在vue中使用HTML-5-拖放API

拖放 API 将可拖动元素增加到 HTML,使咱们能够构建蕴含能够拖动的具备丰盛 UI 元素的 Web 利用。

在本文中咱们将用 Vue.js 构建一个简略的看板利用。看板是一种项目管理工具,使用户能够从头到尾直观地治理我的项目。Trello、Pivotal Tracker 和 Jira 等工具都属于看板利用。

设置看板

运行以下命令创立咱们的看板我的项目:

vue create kanban-board

在创立我的项目时,该抉择只蕴含 Babel 和 ESlint 的默认预设。

实现后,删除默认组件 HelloWorld,将 App 组件批改为空,仅蕴含裸组件模板:

<template> <div></div> </template>
<script>
export default {
  name: 'App',
  components: {},};
</script>
<style></style>

接下来用 Bootstrap 进行款式设置,只需 Bootstrap CSS CDN 就够了。将其增加到 public/index.htmlhead 重。

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
    integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>

在看板中构建 UI 组件

看板的样子应该是这样的:

通常看板要有列和卡片。卡片是要执行的单个我的项目或工作,列用来显示特定卡片的状态。

所以须要创立三个 Vue 组件:一个用于列,一个用于卡片,最初一个用于创立新卡片。

创立 card 组件

先来创立 card 组件。在 /component 目录中创立一个新文件 Card.vue

把上面的代码增加到组件中:

<template>
  <div class="card">
    <div class="card-body">A Sample Card</div>
  </div>
</template>
<script>
export default {};
</script>
<style scoped>
div.card {
  margin-bottom: 15px;
  box-shadow: 0 0 5px #cccccc;
  transition: all ease 300ms;
  background: #fdfdfd;
}
div.card:hover {
  box-shadow: 0 0 10px #aaaaaa;
  background: #ffffff;
}
</style>

这样就创立并设置了卡片组件的款式。不过还没有向组件增加可拖动性能,因为这只是组件的框架。

创立 AddCard 组件

顾名思义,这个组件将负责创立新卡片并将其增加到列中。

/components 目录中创立一个 AddCard.vue 文件,并增加以下代码:

<template>
  <div class="">
    <button
      class="btn btn-sm btn-info w-100"
      v-if="!inAddMode"
      @click="inAddMode = true"
    >
      Add Card
    </button>
    <form action="#" class="card p-3" ref="form" v-else>
      <div class="form-group">
        <input
          type="text"
          name="title"
          id="title"
          class="form-control"
          placeholder="Something interesting..."
          v-model="cardData"
        />
      </div>
      <div class="d-flex justify-content-center">
        <button type="submit" class="btn w-50 btn-primary mr-3">Save</button>
        <button type="reset" class="btn w-50 btn-danger">
          Cancel
        </button>
      </div>
    </form>
  </div>
</template>
<script>
export default {data() {
    return {
      inAddMode: false,
      cardData: '',
    };
  },
  methods: {},};
</script>
<style></style>

具体性能将在前面进行构建。

创立 Column 组件

这是最初一个组件,它用来显示卡列表,还会蕴含 AddCard 组件,以便能够将新卡片间接创立到列中。

components 目录中创立一个 Column.vue 文件,并增加以下代码:

<template>
  <div class="col-md-3 card column" ref="column">
    <header class="card-header">
      <h3 class="col">Column Name</h3>
    </header>
    <div class="card-list"></div>
  </div>
</template>
<script>
export default {};
</script>
<style scoped>
div.column {
  padding: 0;
  padding-bottom: 15px;
  margin: 0 15px;
  box-shadow: 0 0 10px #cccccc;
}
div.card-list {padding: 0 15px;}
header {margin-bottom: 10px;}
header h3 {text-align: center;}
</style>

当初我的项目的框架搭好了,接下来先概述一下拖放性能在浏览器中是怎么工作的。

HTML5 拖放 API 是什么?

当用户将鼠标移到可拖动元素上时,拖动操作开始,而后将元素挪动到启用拖放的元素上。

再默认状况下,惟一可拖动的 HTML 元素是图像和链接。为了使其余元素可拖动,须要通过将 draggable 属性增加到元素;也能够在 JavaScript 中抉择元素并将 draggable 属性设置为 true 来显式创立性能。

在元素上将 draggable 属性设置为 true 之后,你会留神到 draggable 属性已增加到该元素。

<!-- Making an element draggable in HTML -->
<div draggable="true">This is a draggable div in HTML</div>

<script>
// Making an element draggable in javascript
const div = document.querySelector('div');
div.draggable = true;
</script>

拖动元素的目标是将数据从页面的一个局部传输到另一部分。

对于图像,要传输的数据是图像 URL 或它的 base 64 示意模式。如果是链接,传输的数据是 URL。能够将链接挪动到浏览器的 URL 栏中,这样使浏览器跳转到该 URL。

所以,如果没有数据传输的能力,那么拖动元素就毫无用处了。能够通过 DataTransfer API 把通过拖动操作传输的数据保留在拖动数据存储区中,这个 API 提供了在拖放操作期间存储和拜访数据的形式。

DataTransfer 提供了增加要通过拖放传输的我的项目的地位。能够在开始拖动操作时(调用 dragstart 事件时)将数据增加到拖动数据存储中,并且只能在实现拖放操作后(调用 drop 事件时)能力接收数据。

从拖动到开释元素的这段时间中,元素被拖放后,将会在被拖动的元素上触发两个事件:dragstartdragend

当初还不能把可拖动元素拖放到任何中央。与须要显式的使元素可拖动一样,它也须要启用搁置。

要启用元素拖放性能须要侦听 dragover 事件并阻止默认的浏览器操作。

<!-- Make a section drop-enabled -->
<section class="section"></section>
<script>
const section = document.querySelector('.section');
section.addEventListener('dragover', (e) => {e.preventDefault();
});
</script>

将元素拖动到启用拖放的元素上时,将会在启用拖放的元素上触发以下事件:

Dragenter:当一个元素被拖动到启用拖放的元素上时触发一次
Dragover:只有元素依然位于启用了 drop 的元素上,就会间断触发
Drop:在把拖动的元素拖放到启用了拖放的元素上之后触发。

须要留神的是,仅在触发搁置事件时能力拜访存储在 DataTransfer 对象中的数据,而不能在 dragenterdragover 上拜访。

组合所有的组件

在向组件增加拖放性能之前,先讨论一下 app state

这里的 app state 将存储在 App 组件中,而后能够作为 props 向下传递到 Column 组件。另一方面,列组件在渲染时会将所需的 props 传递给卡片组件。

批改 App.vue 使其可能反映状态和组件组成:

// App.vue
<template>
  <div class="container-fluid">
    <h2 class="m-5">
      Vue Kanban Board
    </h2>
    <div class="row justify-content-center">
      <Column
        v-for="(column, index) in columns"
        :column="column"
        :key="index"
      />
    </div>
  </div>
</template>
<script>
import Column from './components/Column';
export default {
  name: 'App',
  components: {Column,},
  data() {
    return {
      columns: [
        {
          name: 'TO-DO',
          cards: [
            {value: 'Prepare breakfast',},
            {value: 'Go to the market',},
            {value: 'Do the laundry',},
          ],
        },
        {
          name: 'In Progress',
          cards: [],},
        {
          name: 'Done',
          cards: [],},
      ],
    };
  },
};
</script>
<style>
h2 {text-align: center;}
</style>

在这里,咱们导入了列组件,并在状态为 columns 的状态下循环拜访数据时,将每一列的数据传递给 column 组件。在这种状况下,只有“To-Do”,“In Progress”和“Done”三列,每列都有一个卡片数组。

接下来,更新 Column 组件来接管 props 并显示它:

// Column.vue
<template>
  <div class="col-md-3 card column" ref="column">
    <header class="card-header">
      <h3 class="col">{{column.name}}</h3>
      <AddCard />
    </header>
    <div class="card-list">
      <Card v-for="(card, index) in column.cards" :key="index" :card="card" />
    </div>
  </div>
</template>
<script>
import Card from './Card';
import AddCard from './AddCard';
export default {
  name: 'Column',
  components: {
    Card,
    AddCard,
  },
  props: {
    column: {
      type: Object,
      required: true,
    },
  },
};
</script>

...

Column 组件从 App 组件接管 props,并用 props 渲染 Card 组件列表。在这里还会应用 AddCard 组件,因为应该能够将新卡间接增加到列中。

最初更新 Card 组件显示从 Column 接管的数据。

// Card.vue
<template>
  <div class="card" ref="card">
    <div class="card-body">{{card.value}}</div>
  </div>
</template>
<script>
export default {
  name: 'Card',
  props: {
    card: {
      type: Object,
      required: true,
    },
  },
};
</script>

Card 组件仅从 Column 接管它须要的所有数据并显示进去。咱们还在此处增加了对 card 元素的援用,这样在用 JavaScript 拜访 card 元素时十分有用。

实现上述操作后,你的利用应该是上面这样了:

增加拖放性能

增加拖放性能的第一步是辨认可拖动组件和搁置指标。

用户应该可能依照卡片中的流动进度将卡片从一列拖到另一列。所以可拖动组件应该是 Card 组件,而搁置指标是 Column 组件。

使卡片可拖动

须要执行以下操作能力使卡组件可拖动:

  1. draggable 属性设置为 true
  2. DataTransfer 对象设置要传输的数据

应该先把 draggable 设置为 true,依据 Vue 生命周期 hook,平安的地位应该是已装置的 hook。把以下内容增加到 Card 组件的已装置 hook 中:

// Card.vue
<script>
export default {
  name: 'Card',
  props: {...},

  mounted() {this.setDraggable();
  },

  methods: {setDraggable() {
      // Get Card element.
      const card = this.$refs.card;
      card.draggable = true;
      // Setup event listeners.
      card.addEventListener('dragstart', this.handleDragStart);
      card.addEventListener('dragend', this.handleDragEnd);
    },
  },
</script>

在下面,咱们创立了一个 setDraggable 办法来使卡片组件可拖动。

setDraggable 中,从上一节中增加的援用中失去卡片,并将 draggable 属性设置为 true

同时还须要设置事件监听器:

// Card.vue
<script>
export const CardDataType = 'text/x-kanban-card';

export default {
...
  methods: {setDraggable() {...},
    handleDragStart(event) {
      const dataTransfer = event.dataTransfer;
      // Set the data to the value of the card which is gotten from props.
      dataTransfer.setData(CardDataType, this.card.value);
      dataTransfer.effectAllowed = 'move';
      // Add visual cues to show that the card is no longer in it's position.
      event.target.style.opacity = 0.2;
    },
    handleDragEnd(event) {
      // Return the opacity to normal when the card is dropped.
      event.target.style.opacity = 1;
    }
  }
}
</script>

在后面提到,只有在 dragstart 事件被调用时,数据才能够被增加到拖动数据存储中。所以须要在 handleDragStart 办法中增加数据。

设置数据时要用到的重要信息是格局,能够是字符串。在咱们的例子中,它被设置为 text/x-kanban-card。存储这个数据格式并导出它,因为在删除卡后获取数据时,Column 组件将会用到它。

最初,将 card 的透明度升高到 0.2,以便向用户提供一些反馈,表明该卡实际上已被拉出其原始地位。拖动实现后,再把透明度复原为 1

当初能够拖动卡片了。接下来增加搁置指标。

dragover 设置为 drop-enabled

将卡片拖到列组件上时,会立刻触发 dragover 事件,将卡放入列中后会触发 drop 事件。

要使卡片掉落到列中,须要侦听这些事件。

// Column.vue
<template>...</template>
<script>
import Card {CardDataType} from './Card';
import AddCard from './AddCard';
export default {
  name: 'Column',
  components: {...},
  props: {...},
  mounted() {this.enableDrop();
  },
  methods: {enableDrop() {
      const column = this.$refs.column;
      column.addEventListener('dragenter', this.handleDragEnter);
      column.addEventListener('dragover', this.handleDragOver);
      column.addEventListener('drop', this.handleDrop);
    },
    /**
     * @param {DragEvent} event
     */
    handleDragEnter(event) {if (event.dataTransfer.types.includes[CardDataType]) {
        // Only handle cards.
        event.preventDefault();}
    },
    handleDragOver(event) {
      // Create a move effect.
      event.dataTransfer.dropEffect = 'move';
      event.preventDefault();},
    /**
     * @param {DragEvent} event
     */
    handleDrop(event) {const data = event.dataTransfer.getData(CardDataType);
      // Emit a card moved event.
      this.$emit('cardMoved', data);
    },
  },
};
</script>

在这里将设置在挂载 Column 组件之后启用 drop 所需的所有事件侦听器。

在这三个事件中,第一个被触发的是 dragenter 当可拖动元素被拖到列中时会立刻被触发。对于咱们的程序,只心愿将卡片放入一列中,所以在 dragenter 事件中,只阻止数据类型的默认值,数据类型包含在 card 组件中所定义的 card 数据类型。

dragover 事件中,把搁置成果设置为 move

在 drop 事件中取得从 dataTransfer 对象传输的数据。

接下来,须要更新状态并将卡片挪动到当前列。因为咱们的程序状态位于 App 组件中,所以在 drop 侦听器中收回 cardMoved 事件,传递已传输的数据,并在 App 组件中侦听 cardMoved 事件。

更新 App.vue 来监听 cardMoved 事件:

// App.vue

<template>
  <div class="container-fluid">
    ...
    <div class="row justify-content-center">
      <Column
        v-for="(column, index) in columns"
        :column="column"
        :key="index"
        @cardMoved="moveCardToColumn($event, column)"
      />
    </div>
  </div>
</template>

<script>
import Column from './components/Column';
export default {
  name: 'App',
  components: {...},
  data() {return {...}
  },
  methods: {moveCardToColumn(data, newColumn) {
      const formerColumn = this.columns.find(column => {
        // Get all the card values in a column.
        const cardValues = column.cards.map((card) => card.value);
        return cardValues.includes(data);
      })
      // Remove card from former column.
      formerColumn.cards = formerColumn.cards.filter((card) => card.value !== data
      );
      // Add card to the new column.
      newColumn.cards.push({value: data});
    },
  },
}
</script>

在这里通过 @cardMoved 侦听 cardMoved 事件,并调用 moveCardToColumn 办法。cardMoved 事件收回一个值(卡片数据),能够通过 $event 拜访这个值,另外还传递了搁置卡的当前列(这是调度事件的地位)。

moveCardToColumn 函数做了三件事:找到卡偏先前所在的列,从该列中取出卡片,最初把卡片加到新列中。

实现看板

当初咱们曾经实现了拖放性能,最初只剩下增加卡片的性能了。

AddCard.vue 中增加以下代码:

<template>
  <div class="">
    <button
      class="btn btn-sm btn-info w-100"
      v-if="!inAddMode"
      @click="inAddMode = true"
    >
      Add Card
    </button>
    <form
      action="#"
      class="card p-3"
      @submit.prevent="handleSubmit"
      @reset="handleReset"
      ref="form"
      v-else
    >
      ...
    </form>
  </div>
</template>
<script>
export default {data() {return {...};
  },
  methods: {handleSubmit() {if (this.cardData.trim()) {
        this.cardData = '';
        this.inAddMode = false;
        this.$emit('newcard', this.cardData.trim());
      }
    },
    handleReset() {
      this.cardData = '';
      this.inAddMode = false;
    },
  },
};
</script>

下面的代码是在提交“add card”表单或重置时运行的函数。

重置后革除 cardData,并将 inAddMode 设置为 false

在提交表单后还要革除 cardData,以便在增加新我的项目时不会显示以前的数据,并且还要将 inAddMode 设置为 false 并收回 newcard 事件。

Column组件中应用了 AddCard 组件,所以须要在 Column 组件中监听 newcard 事件。在 Column 组件中增加侦听 newcard 事件的代码:

<template>
  <div class="col-md-3 card column" ref="column">
    <header class="card-header">
      <h3 class="col">{{column.name}}</h3>
      <AddCard @newcard="$emit('newcard', $event)"></AddCard>
    </header>
    ...
</template>
...

在这里从新收回 newcard 事件,这样能够使它达到 App 组件,理论的动作将在该组件上产生。

自定义 Vue 事件不会冒泡,因而 App 组件无奈侦听 AddCard 组件中收回的 newcard 事件,因为它不是间接子组件。

更新 App 组件解决 newcard 事件的代码:

// App.vue

<template>
  <div class="container-fluid">
    ...
    <div class="row justify-content-center">
      <Column
        v-for="(column, index) in columns"
        :column="column"
        :key="index"
        @cardMoved="moveCardToColumn($event, column)"
        @newcard="handleNewCard($event, column)"
      />
    </div>
  </div>
</template>

<script>
import Column from './components/Column';
export default {
  name: 'App',
  components: {...},
  data() {return {...}
  },
  methods: {moveCardToColumn(data, newColumn) {...},
    handleNewCard(data, column) {
      // Add new card to column.
      column.cards.unshift({value: data});
    },
  },
};
</script>

在这里侦听从 Column 组件调用的 newcard 事件,在获取数据后,创立一个新卡片并将其增加到创立该卡的列中。

总结

在本文中,咱们介绍了什么是 HTML 5 拖放 API,如何应用,以及如何在 Vue.js 中实现。

拖放性能也能够在其余前端框架和原生 JavaScript 中应用。


本文首发微信公众号:前端先锋

欢送扫描二维码关注公众号,每天都给你推送陈腐的前端技术文章


欢送持续浏览本专栏其它高赞文章:

  • 深刻了解 Shadow DOM v1
  • 一步步教你用 WebVR 实现虚拟现实游戏
  • 13 个帮你进步开发效率的古代 CSS 框架
  • 疾速上手 BootstrapVue
  • JavaScript 引擎是如何工作的?从调用栈到 Promise 你须要晓得的所有
  • WebSocket 实战:在 Node 和 React 之间进行实时通信
  • 对于 Git 的 20 个面试题
  • 深刻解析 Node.js 的 console.log
  • Node.js 到底是什么?
  • 30 分钟用 Node.js 构建一个 API 服务器
  • Javascript 的对象拷贝
  • 程序员 30 岁前月薪达不到 30K,该何去何从
  • 14 个最好的 JavaScript 数据可视化库
  • 8 个给前端的顶级 VS Code 扩大插件
  • Node.js 多线程齐全指南
  • 把 HTML 转成 PDF 的 4 个计划及实现

  • 更多文章 …
退出移动版