仿知乎头像上传

2017-02-09 Bruce Wang 更多博文 » 博客 » GitHub »

JavaScript 头像上传

原文链接 http://brucewar.cn/2017/02/09/%E4%BB%BF%E7%9F%A5%E4%B9%8E%E5%A4%B4%E5%83%8F%E4%B8%8A%E4%BC%A0/
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。


啥都不说, 直接先来一张效果图......

头像上传效果图

小伙伴们,别急,咱们先来分析一下主要功能点:

  1. 图片预览
  2. 图片拖拽
  3. 图片缩放
  4. 图片裁剪

图片预览

图片预览的功能技术方案是将用户的图片文件转成Base64编码并设置到<img>标签的src属性,获取图片文件的Base64编码需要通过HTML5的新特性FileReader,具体代码如下:

getImgBase64: function(imgFile, cb){
  if(!window.FileReader){
    alert('系统暂不支持针对你的浏览器的文件上传功能,建议使用最新版的Chrome!');
    return false;
  }
  var reader = new FileReader();
  reader.onload = function(){
    cb && cb(reader.result);
  };
  reader.readAsDataURL(imgFile);
  return true;
}

图片拖拽

图片拖拽的功能需要借助鼠标事件mousedown, mousemove, mouseup,这里贴出mousemove里的处理:

$(document).on('mousemove', function(e){
  e.preventDefault();
  var $thumb = $('.slider-thumb');
  if($thumb.hasClass('moving')){
    // 调整大小
    var left = parseFloat($thumb.css('left'));
    if(self.lastPosition){
      var max = $thumb.siblings('.line').width() - $thumb.width();
      (e.pageX >= self.lastPosition.x)
      ? (left >= max ? left = max : left += (e.pageX - self.lastPosition.x))
      : (left <= 0 ? left = 0 : left += (e.pageX - self.lastPosition.x));
      $thumb.css('left', left);
    }
    self.currentRadio = left / $thumb.siblings('.line').width() + 1;
    self.resizeImg(self.currentRadio);
  }
  var $imgs = $('#avatarEditorDialog').find('img');
  if($imgs.hasClass('moving')){
    // 移动图片位置
    var left = parseFloat($imgs.css('left'));
    var top = parseFloat($imgs.css('top'));
    var width = parseFloat($imgs.css('width'));
    var height = parseFloat($imgs.css('height'));
    if(self.lastPosition){
      var leftMin = -(width - BASE - 30);
      var topMin = -(height - BASE - 30);
      (e.pageX < self.lastPosition.x)
      ? (left <= leftMin ? left = leftMin : left += (e.pageX - self.lastPosition.x))
      : (left >= 30 ? left = 30 : left += (e.pageX - self.lastPosition.x));
      (e.pageY < self.lastPosition.y)
      ? (top <= topMin ? top = topMin : top += (e.pageY - self.lastPosition.y))
      : (top >= 30 ? top = 30 : top += (e.pageY - self.lastPosition.y));
      $imgs.css({
        left: left,
        top: top
      });
    }
  }
  self.lastPosition = {
    x: e.pageX,
    y: e.pageY
  };
});

代码中可以看出,图片拖拽也是有边界的,计算最小的lefttop值。

图片缩放

图片缩放很简单,主要在宽高比固定的条件下,调整图片的宽度和高度。

resizeImg: function(radio){
  var $imgs = $('#avatarEditorDialog').find('img');
  var height = $imgs.height();
  var width = $imgs.width();
  if(height > width){
    $imgs.css({
      width: BASE * radio,
      height: (BASE * height / width) * radio,
      top: -((BASE * height / width) * radio - 310) / 2,
      left: -(BASE * radio - 310) / 2
    });
  }else{
    $imgs.css({
      height: BASE * radio,
      width: (BASE * width / height) * radio,
      top: -(BASE * radio - 310) / 2,
      left : -((BASE * width / height) * radio - 310) / 2
    });
  }
}

图片裁剪

这里的裁剪工作是在前端完成的,需要借助Canvas的一些接口。

var canvas = document.createElement('canvas');
canvas.id = 'avatarCanvas';
var ctx = canvas.getContext('2d');
var $img = $('.avatar-editor-window-inner img');
var originalWidth = parseFloat($img.attr('data-original-width'));
var originalHeight = parseFloat($img.attr('data-original-height'));
var nWidth = $img.width();
var nHeight = $img.height();
var x = (30 - parseFloat($img.css('left'))) * originalWidth / nWidth;
var y = (30 - parseFloat($img.css('top'))) * originalHeight / nHeight;
canvas.width = BASE * originalWidth / nWidth;
canvas.style.width = canvas.width;
canvas.height = BASE * originalHeight / nHeight;
canvas.style.height = canvas.height;
ctx.drawImage($img[0], x, y, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
var avatar = canvas.toDataURL($img.attr('src').match(/data:(.*);base64/)[1] || 'image/jpg');

drawImage方法的功能是将图片上的某个点作为锚点,绘制指定的宽度和高度到Canvas标签。

完整的demo代码请移步至brucewar/avatar-upload

有心的读者可能已经发现,知乎的头像上传功能已经换了一种实现方案。不错,这还是之前的方案,通过上下两张图片实现预览时的边界半透明效果。