在任何拥有用户头像的应用程序中,用户都应该能够更改这些头像。 任何使此过程更轻松的操作都是可取的。 许多应用程序从用户的 Twitter 头像、Facebook 头像或 Gravatar 开始。 这是一个明智的举措。 头像让用户对虚拟空间拥有掌控感,因此以任何方式让他们拥有他们想要的头像都有利于提高用户参与度。
让我们创建一个页面,用户可以在该页面上以最小的阻力更新他们的头像:他们只需将图片拖放到页面上的任何位置,就完成了。

拖放的关键
我们可能要处理的最重要的部分是“drop”事件。 这是我们获取对文件访问权限并执行所需操作的地方。 我们将使用 jQuery 来帮助我们处理事件等。
// Required for drag and drop file access
jQuery.event.props.push('dataTransfer');
$("body").on('drop', function(event) {
// Or else the browser will open the file
event.preventDefault();
// Do something with the file(s)
var files = event.dataTransfer.files;
}
有趣的是,如上所述,这将无法工作。 还需要一个额外的部分,即阻止dragover
事件的默认行为。 这没问题,因为我们将使用该事件来进行某种 UI 更改,以强调“可拖放性”。
$("body").on("dragover", function(event) {
// Do something to UI to make page look droppable
// Required for drop to work
return false;
});
当然,如果用户没有执行拖放操作,请删除该 UI 更改。
$("body").on("dragleave", function(event) {
// Remove UI change
});
处理拖放的文件
拖放可以处理多个文件。 我们这里只处理一个文件,因此让我们只使用第一个文件。
$("body").on('drop', function(event) {
event.preventDefault();
var file = event.dataTransfer.files[0];
if (file.type.match('image.*')) {
// Deal with file
} else {
// However you want to handle error that dropped file wasn't an image
}
}
也许如果你真的很好,你会遍历所有文件并找到第一个图片,而不是根据第一个文件拒绝。
调整头像大小
有一些服务器端方法可以调整图片大小,但这需要往返操作(速度慢)并且会传输可能非常大的文件(速度慢)。 我们可以在客户端直接将头像调整到应用程序所需的大小。 这非常快。
您可以通过创建<canvas>
,将图片绘制到画布,然后将画布导出为数据 URI 来完成此操作。 Andrea Giammarchi 有一个关于此操作的优秀脚本。 您只需要在编写的所有自定义代码之前包含此脚本即可。
将头像变为正方形
在我们的示例中,所有头像都是正方形。 将图片变为正方形有点棘手。 您是否允许矩形并将其居中? 您是否在边缘周围应用空白,以便矩形实际上是正方形? 您是否裁剪图片以使其成为正方形? 如果您进行裁剪,您是从哪个原始点进行裁剪? 您是在调整大小之前还是之后进行裁剪?
- 让我们选择裁剪。
- 让我们完全不要让用户为此操心。 有些方法可以为用户构建 UI 裁剪工具,让他们自己选择裁剪区域,但让我们不要让用户多走一步,只需自动完成即可。
- 让我们从顶部/左侧进行裁剪。
所有这些画布内容让我有点不知所措。 幸运的是,Ralph Holzmann 能够加入进来并帮助我修改 Andrea 的原始脚本以处理裁剪。
裁剪/调整大小的实际操作
有了这些准备工作,我们可以通过调用新的脚本(它同时执行裁剪和调整大小)来裁剪和调整头像大小。
var fileTracker = new FileReader;
fileTracker.onload = function() {
Resample(
this.result,
256,
256,
placeNewAvatar
);
}
fileTracker.readAsDataURL(file);
placeNewAvatar
是一个自定义回调函数,我们将提供该函数,它将接收新调整大小的数据 URI,我们可以将其放置在页面上。
function placeNewAvatar(data) {
$("#profile-avatar").attr("src", data);
}
上传和保存头像
您可能希望保存调整大小后的头像,而不是原始头像。 这样可以保持速度和降低存储空间。
触发页面加载来上传文件会很愚蠢,因为我们已经使用了一种很棒的拖放功能。 因此,我们正在考虑使用 Ajax 将文件发送到服务器。 也许您的服务器完全可以接受和保存该数据 URI。 在这种情况下,只需将其发送到任何位置即可。 例如,
$.post({
url: "/your/app/save/image/whatever",
data: data
});
但如果您使用某种资产主机,它们可能希望使用真实的文件,而不是数据 URI。 并且它们希望您以POST
方式向其发送multipart/form-data
,而不仅仅是一个字符串。 因此,您需要将该数据 URI 转换为文件(或“Blob”)。
function dataURItoBlob(dataURI) {
var binary = atob(dataURI.split(',')[1]);
var array = [];
for (var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], {type: 'image/jpeg'});
}
您也不能再使用 jQuery 来使用 Ajax 发送文件,因为在我看来,它的 Ajax 方法不允许您传递FormData()
。 因此,您需要手动执行此操作,这没问题,因为拖放比 Ajax 更新颖。
var xhr = new XMLHttpRequest();
var fd = new FormData();
fd.append('file', resampledFile);
xhr.open('POST', "/your/app/save/image/whatever", true);
xhr.send(fd);
相关的 CSS 代码
如果您要监视对 body 的拖放事件,则应确保 body 的高度至少与页面相同。 否则,底部可能会有一些区域无法接收事件。
html, body {
height: 100%;
}
此外,拖放事件不仅由从浏览器窗口外部拖入的文件触发,还可以由拖动已放置在页面上的图片触发。 为了防止这种情况,我将图片包裹在一个 div 中,并在整个 div 上应用一个伪元素。 这可以阻止对该图片的拖动操作。
.profile-avatar-wrap {
position: relative;
}
.profile-avatar-wrap:after {
/* Drag Prevention */
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
这同样适用于文件输入
作为备用方案,您也可以包含文件输入。 您希望如何处理它完全取决于您。 例如,在功能检测失败时将其注入,或者只是在页面上同时提供两者。
<input type="file" id="uploader">
一切都会基本相同,只是对文件的访问将发生在
$("#uploader").on('change', function(event) {
var file = event.target.files[0];
});
这对于移动设备尤其重要,因为拖放功能在移动设备上不太相关。
尚未完成
这是演示
我甚至不太确定浏览器的支持情况。 以下是 CanIUse 中关于拖放和画布的说明。
我相信我没有完全完美地处理所有内容。 首先,它似乎在 Opera 中无法正常工作。 拖放功能似乎可以正常工作(它会询问您是否要上传文件),但始终无法处理。
它确实在 Chrome/Safari/Firefox 中可以正常工作。
其次,我通过在 body 中添加一个伪元素来处理“将文件拖放到任何位置”的逻辑。 有时它会有点“闪烁”。 与我尝试使用 div 进行处理相比,这是一个改进,但并不完美。 我还尝试仅在dragleave
事件起源于 body 本身时执行 UI 操作。
$("body").on("dragleave", function(event) {
if (event.currentTarget == $("body")[0]) {
$("body").removeClassClass("droppable");
}
// Required for drop to work
return false;
});
但没有用。
最后,本文中显示的代码没有经过组织。 在实际应用中,您需要将所有这些代码进行组织。 以下是经过组织的代码。 如果您能修复问题或使其更完善,我已将其发布到 GitHub,因此请随时提交拉取请求(如何)。
这种上传方式的安全性如何?是否存在需要担心的安全漏洞?
您在客户端进行调整大小/裁剪,然后将其发送到服务器端。如果没有在服务器端进行验证,任何人都可以上传任何内容,任何图像,任何大小等等。在我看来,这篇文章只是证明了它可以在客户端完成(并且可以用于节省上传大图像的时间,如文中所述),但你不应该依赖上传的内容,你不能信任任何发送到“/your/app/save/image/whatever”的内容。
太棒了!我一定会在不久的将来用到它。
在 Mac 上的 Chrome 23 中发现了一个小错误:使用文件上传更改头像后,我拖放了新的图片。当我拖动到放置区域上时,放置区域会反复闪烁。这只会发生在使用文件上传后的第一次。不知道如何修复,但也许这里有人知道。
我指的是“这里有人知道”……
我发现了同样的错误,不过是在 Mac 上的 Chrome 24 中,不知道是什么原因造成的?
当在 body 上触发 dragover 状态时,会添加一个 :after 元素,这会导致 body 丢失 dragover 状态。
然后就会出现癫痫。
没错。我在文章中提到了这个问题。在我看来,当你使用 DIV 或其他元素时,这个问题会更严重。
剩下的问题是
1) 为什么一个伪元素会触发 dragleave?
2) 即使你检查了 event.currentTarget 并确保它是 body(很奇怪),这种情况也会发生
3) 解除绑定/重新绑定是答案吗?通常情况下,答案是否定的……
我实际上用过的一种方法是,只对放置区域做一些视觉效果不那么强烈的事情来突出显示它。这样即使它闪烁,也不会导致癫痫。
1 & 2. 也许它的工作原理类似于 mouseover 和 mouseout。
这种闪烁可能与之相关(尽管我想相信它是相关的),但有一半的时候,当我放下图片时,“drop”事件不会触发,我只能加载图片,就好像我把书签拖到了页面上一样。
为了解决过去出现的随机闪烁问题,我将 dragleave 事件设置为不直接调用离开操作,而是使用一个延迟 200 毫秒的 setTimeout 调用离开操作。然后在 dragenter 和 dragover 事件中,在你做任何其他事情之前清除该超时。我从一个拖放教程中学习了这种技巧,它一直为我服务。
对于 :after 伪元素的问题,也许可以给它设置一个 pointer-events:none?
这两个想法都很棒。我正在尝试这两种方法。
我相当肯定 Opera 还不支持 FormData() 函数,这会阻止上传代码运行。不出所料,我认为任何 IE(也许 IE10 可以)都不支持你在上传中使用的新的 XMLHttpRequest 2。Remy Sharp 在他的文章中也提到了这一点:http://html5doctor.com/drag-and-drop-to-server/
感谢您提供精彩的写作!
您是否有关于拖放和常规文件选择器受欢迎程度的统计数据?我总是将浏览器设置为全屏模式,所以对我来说,文件选择器总是更容易使用。
如果 IE8+ 支持它,它实际上是有意义的,因为这是核心功能。
尽管如此,这篇文章非常有趣,而且我们(服务器)不得不将 9MP 的图片调整为 100x100px,这真是令人发狂。
我一直试图将它添加到我正在开发的个人资料页面中。我认为我做的一切都正确。我把文件上传到了我的测试服务器上,路径是对的。
当我拖放图片时,它会突出显示头像区域,或者当我点击选择文件按钮时,它会允许我选择文件。但是,无论我做哪种操作,图片都不会加载。
我可能遗漏了什么步骤吗?
我正在使用最新版本的 Chrome
在我和我的兄弟的 Safari 上都无法正常工作。我在 PC 上,他在 Mac 上。我的版本是 v. 5.1.7
为了只实现从操作系统到浏览器的图片拖放,我在一年前做了以下事情
演示链接
想请您查看一下。另外,我使用了通过 base64 编码图片进行的 DataURI 转换,这在您的演示中的图片 src 中也是可见的。我认为用这种方式存储如此大的编码字符串非常糟糕。还有什么其他选择吗?
效果很好
感谢 Chris :D
我想在一个包含多个头像的页面中实现它,这些头像可以更改,我需要修改什么才能只拖放和更改一个图片,现在它正在更改页面中的所有头像。