【高级功能】使用拖放

HTML5 添加了对拖放(drag and drop)的支持。我们之前只能依靠jQuery 这样的JavaScript库才能处理这种操作。把拖放内置到浏览器的好处是它可以正确的集成到操作系统中,而且正如将要看到的,它能跨浏览器工作。

1. 创建来源项目

我们通过 draggable属性告诉浏览器文档里的哪些元素可以被拖动。这个值有三个允许的值:

它的默认值是auto,即把决定权交给浏览器,通常来说这就意味着所有元素默认都是可拖动的,我们必须显示设置draggable 属性为false 来禁止拖动。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>定义可拖放项目</title>
    <style>
        #src > * {float: left;}
        #target,#src > img {border: thin solid black;padding: 2px;margin: 4px;}
        #target {height: 81px;width: 81px;text-align: center;display: table;}
        #target > p {display: table-cell;vertical-align: middle;}
        #target > img {margin: 1px;}
    </style>
</head>
<body>
<div id="src">
    <img draggable="true" id="apple" src="../imgs/apple.png" alt="apple" />
    <img draggable="true" id="banana" src="../imgs/banana-small.png" alt="banana" />
    <img draggable="true" id="lemon" src="../imgs/lemon100.png" alt="lemon" />
    <div id="target">
        <p>Drop Here</p>
    </div>
</div>
<script>
    var src = document.getElementById("src");
    var target = document.getElementById("target");
</script>
</body>
</html>

此例里有三个img元素,每一个的draggable 的属性都被设为true。这里还创建了一个id为target的div元素,稍后将设置它用来接收我们拖动的img元素。从下图可以看到这个文档再来浏览器里的样子。

我们不需要再做任何设置就能拖动水果图像,但浏览器会提示我们不能把它们释放到任何地方。通常的做法是展示一个禁止进入的标准作为光标,如下图所示:

处理拖动事件

我们通过一系列事件来利用拖放功能

我们可以用这些事件在视觉上强调拖动操作,如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>使用针对被拖动元素的事件</title>
    <style>
        #src > * {float: left;}
        #target,#src > img {border: thin solid black;padding: 2px;margin: 4px;}
        #target {height: 81px;width: 81px;text-align: center;display: table;}
        #target > p {display: table-cell;vertical-align: middle;}
        #target > img {margin: 1px;}
        img.dragged {background-color: lightgrey;}
    </style>
</head>
<body>
<div id="src">
    <img draggable="true" id="apple" src="../imgs/apple.png" alt="apple" />
    <img draggable="true" id="banana" src="../imgs/banana-small.png" alt="banana" />
    <img draggable="true" id="lemon" src="../imgs/lemon100.png" alt="lemon" />
    <div id="target">
        <p id="msg">Drop Here</p>
    </div>
</div>
<script>
    var src = document.getElementById("src");
    var target = document.getElementById("target");
    var msg = document.getElementById("msg");
    src.ondragstart = function(e){
        e.target.classList.add("dragged");
    }
    src.ondragend = function(e){
        e.target.classList.remove("dragged");
        msg.innerHTML = "Drop Here";
    }
    src.ondrag = function(e){
        msg.innerHTML = e.target.id;
    }
</script>
</body>
</html>

此例定义了一个新的CSS样式,它会被应用到属于dragged类的元素上。在dragstart事件触发时把拖动的元素添加到这个类中,在dragend事件触发时把它从类中移除。作为对drag事件的响应,这里把释放区里显示的文本设为被拖动元素的id值。在拖动操作过程中,drag事件每隔几毫秒就会触发以此,所以这不是最有效率的技巧,但它确实能演示这个事件。此例的显示效果如下:

2. 创建释放区

要让某个元素成为释放区,我们需要处理 dragenter和 dragover事件。它们是针对释放区的其中两个事件。

dragenter和 dragover 事件的默认行为是拒绝接受任何被拖放的项目,因此我们必须要做的最重要的事就是防止这种默认行为被执行。

PS:拖放功能的规范告诉我们还必须想要称为释放区的元素应用dropzone属性,而且此属性的值应当包含我们愿意接受的操作与数据类型细节。浏览器实际上不是这么实现拖放功能的。

修改前面例子的JavaScript代码如下:

<script>
    var src = document.getElementById("src");
    var target = document.getElementById("target");
    var msg = document.getElementById("msg");

    target.ondragenter = handleDrag;
    target.ondragover = handleDrag;

    function handleDrag(e){
        e.preventDefault();
    }

    src.ondragstart = function(e){
        e.target.classList.add("dragged");
    }
    src.ondragend = function(e){
        e.target.classList.remove("dragged");
        msg.innerHTML = "Drop Here";
    }
    src.ondrag = function(e){
        msg.innerHTML = e.target.id;
    }
</script>

添加这些代码后,我们就有了一个活动的释放区。当我们拖动一个项目到释放区元素上时,浏览器会提示如果我们放下时它就会被接受,如下图所示:

接受释放

我们通过处理drop事件来接收释放的元素,它会在某个项目被放到释放区元素上时触发。下面的例子展示了如何处理响应drop事件,具体的做法是使用一个全局变量作为被拖动元素和释放区之间的桥梁。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>处理drop事件</title>
    <style>
        #src > * {float: left;}
        #target,#src > img {border: thin solid black;padding: 2px;margin: 4px;}
        #target {height: 81px;width: 81px;text-align: center;display: table;}
        #target > p {display: table-cell;vertical-align: middle;}
        #target > img {margin: 1px;}
        img.dragged {background-color: lightgrey;}
    </style>
</head>
<body>
<div id="src">
    <img draggable="true" id="apple" src="../imgs/apple.png" alt="apple" />
    <img draggable="true" id="banana" src="../imgs/banana-small.png" alt="banana" />
    <img draggable="true" id="lemon" src="../imgs/lemon100.png" alt="lemon" />
    <div id="target">
        <p id="msg">Drop Here</p>
    </div>
</div>
<script>
    var src = document.getElementById("src");
    var target = document.getElementById("target");
    var msg = document.getElementById("msg");

    var draggedID;

    target.ondragenter = handleDrag;
    target.ondragover = handleDrag;

    function handleDrag(e){
        e.preventDefault();
    }

    target.ondrop = function(e){
        var newElem = document.getElementById(draggedID).cloneNode(false);
        target.innerHTML = "";
        target.appendChild(newElem);
        e.preventDefault();
    }

    src.ondragstart = function(e){
        draggedID = e.target.id;
        e.target.classList.add("dragged");
    }
    src.ondragend = function(e){
        var elems = document.querySelectorAll(".dragged");
        for (var i=0;i<elems.length;i++){
            elems[i].classList.remove("dragged");
        }
    }
</script>
</body>
</html>

此例在dragstart事件触发时设置了变量draggedID 的值。这能够记录被拖动元素的id属性值。当drop事件触发时,用这个值克隆了被拖动的img元素,把它添加为释放区元素的一个子元素。其显示效果如下:

3. 使用DataTransfer对象

与拖放操作所触发的事件同时派发的对象是DragEvent,它派生于MouseEvent。DragEvent对象定义了Event与MouseEvent对象的所有功能,并额外增加了 dataTransfer 属性,用来返回用于传输数据到释放区的DataTransfer对象。

我们可以用DataTransfer对象从被拖动元素传输任意数据到释放区元素上。DataTransfer对象定义的属性和方法如下表所示:

在上个例子中,克隆了元素本身。但DataTransfer对象允许我们使用一种更为复杂的方式。我们能做的第一件事是用DataTransfer对象从被拖动元素传输数据到释放区,如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>使用DataTransfer对象传输数据</title>
    <style>
        #src > * {float: left;}
        #target,#src > img {border: thin solid black;padding: 2px;margin: 4px;}
        #target {height: 81px;width: 81px;text-align: center;display: table;}
        #target > p {display: table-cell;vertical-align: middle;}
        #target > img {margin: 1px;}
        img.dragged {background-color: lightgrey;}
    </style>
</head>
<body>
<div id="src">
    <img draggable="true" id="apple" src="../imgs/apple.png" alt="apple" />
    <img draggable="true" id="banana" src="../imgs/banana-small.png" alt="banana" />
    <img draggable="true" id="lemon" src="../imgs/lemon100.png" alt="lemon" />
    <div id="target">
        <p id="msg">Drop Here</p>
    </div>
</div>
<script>
    var src = document.getElementById("src");
    var target = document.getElementById("target");

    target.ondragenter = handleDrag;
    target.ondragover = handleDrag;

    function handleDrag(e){
        e.preventDefault();
    }

    target.ondrop = function(e){
        var droppedID = e.dataTransfer.getData("Text");
        var newElem = document.getElementById(droppedID).cloneNode(false);
        target.innerHTML = "";
        target.appendChild(newElem);
        e.preventDefault();
    }

    src.ondragstart = function(e){
        e.dataTransfer.setData("Text", e.target.id);
        e.target.classList.add("dragged");
    }
    src.ondragend = function(e){
        var elems = document.querySelectorAll(".dragged");
        for (var i=0;i<elems.length;i++){
            elems[i].classList.remove("dragged");
        }
    }
</script>
</body>
</html>

此例在响应dragstart事件时用setData方法设置了想要传输的数据。第一个蚕食指定了数据的类型,它只支持两个值: Text和 Url。第二个参数是我们想要传输的数据(此例中是被拖动元素的id属性)。为了获取它的值,使用了getData方法,并把数据类型作为参数。

你可能会觉得奇怪:为什么这种方式比使用全局变量更好?答案是它能跨浏览器工作。这么说的意思不是指跨同一个浏览器了IDE窗口或标签页,而是横跨不同类型的浏览器。这意味着我们可以从 Chrome浏览器的文档拖动一个元素,然后再Firefox浏览器的文档里释放它,因为拖放功能的支持是集成在操作系统里的,有着相同的特性。如果你打开一个文本编辑器,输入单词 apple,选中它然后拖动到浏览器的释放区,你就会看到苹果的图像被显示出来,效果和我们拖动同一个文档里的某个img元素一样。效果如下:

3.1 根据数据过滤被拖动的项目

可以用DataTransfer对象里存放的数据来选择我们愿意在释放区接受哪些种类的元素。修改上一个示例的JavaScript代码如下:

<script>
    var src = document.getElementById("src");
    var target = document.getElementById("target");

    target.ondragenter = handleDrag;
    target.ondragover = handleDrag;

    function handleDrag(e){
        if(e.dataTransfer.getData("Text") == "banana"){
            e.preventDefault();
        }
    }

    target.ondrop = function(e){
        var droppedID = e.dataTransfer.getData("Text");
        var newElem = document.getElementById(droppedID).cloneNode(false);
        target.innerHTML = "";
        target.appendChild(newElem);
        e.preventDefault();
    }

    src.ondragstart = function(e){
        e.dataTransfer.setData("Text", e.target.id);
        e.target.classList.add("dragged");
    }
    src.ondragend = function(e){
        var elems = document.querySelectorAll(".dragged");
        for (var i=0;i<elems.length;i++){
            elems[i].classList.remove("dragged");
        }
    }
</script>

此例从DataTransfer对象获得数据值,然后检查它是什么。此例表明只有当数据值是banana时才愿意接受被拖动的元素。这么做的效果是过滤掉了苹果和柠檬的图像。当用户拖动这些图像当释放区时,浏览器会提示它们不能被释放。

PS:这种过滤方式测试暂时不可用,估计和浏览器的支持度有关。

3.2 拖放文件

另一种隐藏在浏览器里的HTML5新功能被称为文件API,它允许我们使用本机文件,不过是以严格受控的方式。部分控制来自于我们通常不直接与文件API进行交互,而是使它通过其他功能显露出来,包括拖放功能。下面的示例展示了如何使用文件API,在用户从操作系统里拖动文件并放入释放区时做出响应。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>处理文件</title>
    <style>
        body > * {float: left;}
        #target {border:medium double black;margin: 4px;height: 75px;width: 200px;text-align: center;display: table;}
        #target > p {display: table-cell;vertical-align: middle;}
        table {margin: 4px;border-collapse: collapse;}
        th,td {padding: 4px;}
    </style>
</head>
<body>
<div id="target">
    <p id="msg">Drop Files Here</p>
</div>
<table id="data" border="1"></table>
<script>
    var target = document.getElementById("target");
    target.ondragenter = handleDrag;
    target.ondragover = handleDrag;
    function handleDrag(e){
        e.preventDefault();
    }
    target.ondrop = function(e){
        var files = e.dataTransfer.files;
        var tableElem = document.getElementById("data");
        tableElem.innerHTML = "<tr><th>Name</th><th>Type</th><th>Size</th></tr>";
        for(var i=0;i<files.length;i++){
            var row = "<tr><td>"
                    + files[i].name+"</td><td>"
                    + files[i].type+"</td><td>"
                    + files[i].size+"</td></tr>";
            tableElem.innerHTML += row;
        }
        e.preventDefault();
    }
</script>
</body>
</html>

当用户把文件放入释放区时,DataTransfer对象的文件属性会返回一个FileList对象。我们可以将它视为一个由File对象构成的数组,每个对象都代表用户释放的一个文件(用户可以选择多个文件然后一次性释放它们)。下表展示了File对象的属性:

此例里,script 枚举了放入释放区的文件,并在表格里显示了File属性的值。此例的运行效果如下:

在表单里上传被释放文件

我们可以结合拖放功能、文件API和用Ajax请求上传数据,让用户能从操作系统拖动想要在表单里提交的文件。下面的是示例对此进行演示,新建HTML文档如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>结合拖放、文件API和FormData对象</title>
    <style>
        body > * {float: left;}
        #target {border:medium double black;margin: 4px;height: 75px;width: 200px;text-align: center;display: table;}
        #target > p {display: table-cell;vertical-align: middle;}
        table {margin: 4px;border-collapse: collapse;}
        th,td {padding: 4px;}
    </style>
</head>
<body>
<form id="form1" method="post" action="http://localhost:53396/ajax/html5/fileupload.aspx">
<div id="target">
    <p id="msg">Drop Files Here</p>
</div>
<table id="data" border="1"></table>
<button id="submit" type="submit">Submit Form</button>
</form>
<script>
    var target = document.getElementById("target");
    var httpRequest;
    var fileList;
    document.getElementById("submit").onclick = handleButtonPress;
    target.ondragenter = handleDrag;
    target.ondragover = handleDrag;
    function handleDrag(e){
        e.preventDefault();
    }
    target.ondrop = function(e){
        fileList = e.dataTransfer.files;
        var tableElem = document.getElementById("data");
        tableElem.innerHTML = "<tr><th>Name</th><th>Type</th><th>Size</th></tr>";
        for(var i=0;i<fileList.length;i++){
            var row = "<tr><td>"
                    + fileList[i].name+"</td><td>"
                    + fileList[i].type+"</td><td>"
                    + fileList[i].size+"</td></tr>";
            tableElem.innerHTML += row;
        }
        e.preventDefault();
    }
    function handleButtonPress(e){
        e.preventDefault();
        var form = document.getElementById("form1");
        var formData = new FormData(form);
        if(fileList || true){
            for(var i=0;i<fileList.length;i++){
                formData.append("file"+i,fileList[i]);
            }
        }
        httpRequest = new XMLHttpRequest();
        httpRequest.onreadystatechange  = handleResponse;
        httpRequest.open("POST",form.action);
        httpRequest.send(formData);
    }
    function handleResponse(){
        if(httpRequest.readyState == 4 && httpRequest.status == 200){
            alert(httpRequest.responseText);
        }
    }
</script>
</body>
</html>

这个例子里用FormData.append方法添加那些放入释放区的文件,并传递一个File对象作为此方法的第二个参数。当表单被提交时,文件内容会作为表单请求的一部分自动上传到服务器。

这里的服务器用.NET实现,新建Web项目,并新建 fileupload.aspx 文件,其cs代码如下:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Web4Luka.Web.ajax.html5
{
    public partial class fileupload : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            string tip = "上传失败!";
            string path;
            if (Request.HttpMethod == "POST")
            {
                if (Request.ContentType.IndexOf("multipart/form-data") > -1)
                {
                    if (Request.Files.Count > 0)
                    {
                        for (int i = 0; i < Request.Files.Count; i++)
                        {
                            HttpPostedFile file = Request.Files[i];
                            path = Server.MapPath("/upload/files/" + file.FileName);
                            if (File.Exists(path))
                            {
                                File.Delete(path);
                            }
                            file.SaveAs(path);
                        }
                        tip = "上传成功!";
                    }
                }

                Response.AddHeader("Access-Control-Allow-Origin", "http://localhost:63342");
                Response.Write(tip);
            }
        }
    }
}

注意在服务器端创建对应上传文件保存的文件夹,此例的运行效果如下图所示:

时间: 2024-12-24 18:57:06

【高级功能】使用拖放的相关文章

PHP命名空间规则解析及高级功能

日前发布的PHP 5.3中,最重要的一个新特性就是命名空间的加入.本文介绍了PHP命名空间的一些术语,其解析规则,以及一些高级功能的应用,希望能够帮助读者在项目中真正使用命名空间. 在这里中我们介绍了PHP命名空间的用途和namespace关键字,在这篇文章中我们将介绍一下use命令的使用以及PHP如何解析命名空间的名字的. 为了便于对比,我定义了两个几乎一样的代码块,只有命名空间的名字不同. < ?php // application library 1 namespace App\Lib1;

block高级功能

/* -*- c++ -*- */ /* * Copyright 2004,2007,2009,2010,2013 Free Software Foundation, Inc. * * This file is part of GNU Radio * * GNU Radio is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License a

MVC5+EF6--12 Entity Framework高级功能

近期学习MVC5+EF6,找到了Microsoft的原文,一个非常棒的系列,Getting Started with Entity Framework 6 Code First using MVC 5,网址:http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-an-entity-framework-data-model-for-an-asp-net-mvc-appli

iOS开发——UI篇Swift篇&amp;玩转UItableView(二)高级功能

UItableView高级功能 1 class UITableViewControllerAF: UIViewController, UITableViewDataSource, UITableViewDelegate { 2 3 var titleString:String! 4 5 @IBOutlet var titleLabel:UILabel! 6 @IBOutlet var listTableView : UITableView! 7 @IBOutlet var editDoneBut

[渣译文] 使用 MVC 5 的 EF6 Code First 入门 系列:为ASP.NET MVC应用程序使用高级功能

这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译,这里是第十二篇:为ASP.NET MVC应用程序使用高级功能 原文:Advanced Entity Framework 6 Scenarios for an MVC 5 Web Application 译文版权所有,谢绝全文转载--但您可以在您的网站上添加到该教程的链接. 在之前的教程中,您已经实现了继承.本教程引入了当你在使用实体框架Code

打开phpmyadmin显示高级功能尚未完全设置部分功能未激活

问题:老师,打开phpmyadmin显示高级功能尚未完全设置部分功能未激活,应该如何解决? 这是前一阵子学生问过我的一个问题,今天我就在博客里解答你的疑问吧. 总共三步可以搞定 1.导入相关文件到数据库 2.更改配置文件config.inc.php 3.给于root用户相关权限 详细过程如下: 先找到 phpMyAdmin所在目录,在 phpMyAdmin 目录下有个examples文件夹,该文件夹里面有个 create_tables.sql 数据库文件.(如果你的 phpMyAdmin 是老版

项目开发--&gt;高级功能汇总

祭奠曾经逝去的青春…… 1.高级功能汇总-->Memcached之ASP.NET实现

为SSD编程(4)——高级功能和内部并行

原文 http://codecapsule.com/2014/02/12/coding-for-ssds-part-4-advanced-functionalities-and-internal-parallelism/ 在这个部分,我将简要的介绍一些SSD的主要功能,如TRIM和预留空间.我同样会介绍SSD中不同等级的内部并行. 5. 高级功能 5.1 TRIM 让我们假设一个程序向SSD所有的逻辑块地址都写入文件,这个SSD当然会被装满.然后删除这些文件.文件系统会报告所有的地方都是空的,尽

十二、Python高级功能之Mysql数据库模块

Python高级功能之Mysql数据库模块 安装python mysql组件 # yum -y install MySQL-python.x86_64 以下根据实例来说明: >>> import MySQLdb >>> conn = MySQLdb.connect(user='root',passwd='2wdc%RDX',host='localhost')  #连接数据库(到服务器的连接) >>> cur = conn.cursor()  # 创建游

MVC5 Entity Framework学习之Entity Framework高级功能

在之前的文章中,你已经学习了如何实现每个层次结构一个表继承.本节中你将学习使用Entity Framework Code First来开发ASP.NET web应用程序时可以利用的高级功能. 在本节中你将重用之前已经创建的页面,接下来你需要新建一个页面并使用原始SQL来批量更新数据库中所有Course的学分. 在Department Edit页面中添加新的验证逻辑并使用非跟踪查询. 执行原始SQL查询 Entity FrameworkCode First API包含有可以让你直接向数据库发送SQ