使用 HTML5 拖放更改头像,包括调整大小和裁剪

Avatar of Chris Coyier
Chris Coyier 发布

DigitalOcean 提供适合您旅程各个阶段的云产品。 立即开始使用 价值 200 美元的免费信用额度!

在任何拥有用户头像的应用程序中,用户都应该能够更改这些头像。 任何使此过程更轻松的操作都是可取的。 许多应用程序从用户的 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 有一个关于此操作的优秀脚本。 您只需要在编写的所有自定义代码之前包含此脚本即可。

将头像变为正方形

在我们的示例中,所有头像都是正方形。 将图片变为正方形有点棘手。 您是否允许矩形并将其居中? 您是否在边缘周围应用空白,以便矩形实际上是正方形? 您是否裁剪图片以使其成为正方形? 如果您进行裁剪,您是从哪个原始点进行裁剪? 您是在调整大小之前还是之后进行裁剪?

  1. 让我们选择裁剪。
  2. 让我们完全不要让用户为此操心。 有些方法可以为用户构建 UI 裁剪工具,让他们自己选择裁剪区域,但让我们不要让用户多走一步,只需自动完成即可。
  3. 让我们从顶部/左侧进行裁剪。

所有这些画布内容让我有点不知所措。 幸运的是,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,因此请随时提交拉取请求(如何)。