百度前端技术学院是一个为大学生创办的免费的前端技术实践、分享、交流平台。由百度校园招聘组、百度校园品牌部、百度前端技术部以及多个百度的前端团队联合创办。学院组织了一批百度在职工程师,精心编写了数十个实践编码任务,将技术知识点系统有机地串联在各个充满趣味与挑战的任务中,同学们通过实际地编码练习来掌握知识,再辅以互相评价、学习笔记等方式,加深对于学习内容的理解。在过去的三年中,百度前端技术学院累积吸引了上万名同学参加,并且有数十名同学在学习后,顺利加入了百度,成为了百度的前端工程师。

裁剪器功能分解

作者李涛课程UI组件之图片裁剪器2218次浏览32017-03-30 22:59

刚看到任务时完全懵了,不知道从何下手,在参考了别人的代码后自己完成简单的裁剪器,记录下每个功能点的分析。

绘制背景

要求选择图片后,没有填充图片的区域用透明背景填充。存在这个需求,肯定就要用到canvas了。原理其实挺简单的,就是在canvas上绘制黑色方块,只要绘制时的位置不同即可,白色部分是没有绘制的区域。

下面在一个宽为 600,高为 360 的容器内,绘制宽度为 40 的方块。

const canvas = document.querySelector('#bg')
const ctx = canvas.getContext('2d')
// canvas 宽高
const width = 600
const height = 360
// 黑色方块宽度为 10
let size = 10 
// 在 x 轴和 y 轴上的数量,向上取整
let xNumber = Math.ceil(600 / size)
let yNumber = Math.ceil(360 / size)
ctx.fillStyle = '#ccc'
for (let i = 0; i < yNumber; i++) {
    for (let j = 0; j < xNumber; j++) {
        if ((j + i) % 2 === 0) {
                    ctx.fillRect(j * size, i * size, size, size)
        }
    }
}
// 填充透明度为 0.1 的黑色遮罩
imgCtx.fillStyle = 'rgba(0, 0, 0, 0.1)'
imgCtx.fillRect(0, 0, 600, 360)

重点在if ((j + i) % 2 === 0) {...}代码,如果是偶数就跳过不渲染,所以就有了白色的方块,不过具体原理是什么呢?

来仔细观察一副简单的背景:
简单背景

第一行,i = 0
第二行,i = 1

第一行是奇数渲染,偶数不渲染,而第二行是偶数渲染奇数不渲染。那可以这样做判断:奇数行渲染奇数位,偶数行渲染偶数位。

if ((i % 2 === 0 && j % 2 === 0) || (i % 2 === 1 && j % 2 === 1))  {
    //...
}

和上面的代码同样效果,代码来自 - ding-js 的 github

上传图片

这里指的是将本地图片“上传”到网页上。

首先是监听inputonchange事件,每次点击按钮选择图片都会触发该事件。在事件对象上可以获取到图片。

document.querySelector('#input').onchange = function (event) {
  // 这就是上传的图片
  const file = event.target.files[0]
}

然后要用到FileReader了,首先实例化一个FileReader对象,监听该对象的onload事件,指图片加载完成事件。

那怎么样才能够触发onload事件呢,需要调用readAsDataURL方法,参数为上面获得的file

const fileReader = new FileReader()
// 上面的 onchange
document.querySelector('#input').onchange = function (event) {
  // 这就是上传的图片
  const file = event.target.files[0]
  fileReader.readAsDataURL(file)
}

这样就能够触发fileReader.onload,所以在这之前可以声明好该事件。一般是在该事件内将图片地址赋给页面上的img元素,达到显示图片的目的。

实际例子:https://ltaoo.github.io/ife/uiComponents/task03/views/uploadImg.html

可移动的裁剪框

可以使用一个节点绝对定位,也可以直接在 canvas 上绘制。这里采用的是新增了一个节点。

监听该节点的onmousedownonmouseuponmousemove事件,重点在onmousemove事件,当鼠标在节点内移动时,就会触发该函数。

在该函数内获取当前鼠标的位置,减去父容器的偏移,再减去鼠标在裁剪框节点内的相对位置即可。

裁剪框位置

当裁剪框移动时,肯定不能够超出父容器,处理方案也很简单,只要判断根据前面计算出来的x值不能小于0或者大于父容器的宽度减去裁剪框的宽度。

高度同理。

https://ltaoo.github.io/ife/uiComponents/task03/views/selector.html

预览区

预览区其实和图片区一样,都是使用ctx.drawImage()方法,渲染同一张图片,区别仅仅是渲染大小以及位置不同。

预览的原理

参考 - drawImage

发送图片数据到后端

任务要求能够将数据发送给后端,后端接收到数据后,使用后端在本地生成该图片。如果不考虑自己写后端,前端部分代码就是从预览区获取到图片,发送给后台即可,当然这里要做一些处理:

// canvas 是预览的 canvas
let data = canvas.toDataURL()
data = data.split(',')[1]
data = window.atob(data)
let ia = new Uint8Array(data.length)
for (var i = 0; i < data.length; i++) {
    ia[i] = data.charCodeAt(i)
}
const blob = new Blob([ia], {
    type:"image/png",
    endings:'transparent'
})
// 组建要 post 的数据
const fd = new FormData()
fd.append('filename', blob, 'image.png')
// < ======= 到这里都可以说是固定写法 
fetch('/upload', {
    method: 'POST',
    body: fd
})
.then(response => {
    console.log(response)
    alert('上传成功')
})
.catch(err => {
    console.log(err)
    alert('上传失败')
})

可能做这个任务最麻烦的就是这个需求了,还要自己写后端会耗费大量时间,当然可以使用我已经写好的,也是查看别人的文档自己摸索着写出来的。

这个项目怎么使用呢,先下载文件 https://pan.baidu.com/s/1nvuhlUD ,解压到任意目录,进入解压后的目录,使用

npm i

如果安装很慢,可以搜索如何配置淘宝镜像。

安装项目依赖,安装完成后使用

npm start

开启项目,然后就可以打开浏览器,访问127.0.0.1:3000,就能够看到页面了,显示的页面是views文件夹中的index.html,可以向/upload发送post请求,就能够在项目目录下看到新生成了一个文件,不过由于没有后缀名所以看起来不是图片,但实际上就是图片。

可以将自己写好的前端文件,html 文件放在views文件夹下替换index.html,必须是该文件名;如果有 js 和 css,则需要放到assets文件夹下,等于和index.html放在同目录。

总结

其实这个任务更多的是学习 canvas 的用法,如何绘制方块,如何绘制指定图片,如何获取图片指定区域等等。

经过这次的任务,对这些肯定能够有所了解了。当然上面仅仅能够完成一个最简单的图片裁剪上传,还有很多需要优化的地方:

  • 蒙版遮罩
  • 裁剪框缩放
0条评论