需求
这篇公众号文章是用typora上写的,这是一款大名鼎鼎的客户端markdown编辑器。
Markdown的语法简洁明了、学习容易,而且功能比纯文本更强,因此有很多人用它写博客。世界上最流行的博客平台WordPress和大型CMS如Joomla、Drupal都能很好的支持Markdown。完全采用Markdown编辑器的博客平台有Ghost和Typecho。 除此之外,由于我们有了RStudio这样的神级编辑器,我们还可以快速将Markdown转化为演讲PPT、Word产品文档、LaTex论文甚至是用非常少量的代码完成最小可用原型。在数据科学领域,Markdown已经广泛使用,极大地推进了动态可重复性研究的历史进程。
有了markdown,我彻底抛弃了word。
但是markdown的图片正常来说需要联网。尤其是公开发布的文档。联网时必备的条件。
市面上也有很多markdown编辑软件。我也在寻求好用的markdown编辑器。
我尝试过保存到为知笔记/网易云笔记/印象笔记。但是体验其实都非常差。尤其是我这种长篇累牍都文章。到最后都非常卡。
不知你是否用过博客园的markdown编辑器?印象最深的相信就是"丑陋"吧。但是博客园有一个很好的功能,就是使用微信或qq截图之后,ctrl v,就自动生成了markdown格式的链接地址。这是其他笔记应用没有的。
代码语言:javascript复制![](https://img2018.cnblogs.com/blog/1011161/201908/1011161-20190814204338058-791418648.png)
这段markdown代码在经过渲染后就是一张图了。
我很喜欢如是这般把博客园作为自己的图床。直到某天外网访问它403了。
现在有了服务器,还写了一款markdown编辑器用于发布文档。
就自己实现一个图床吧!
项目基于vue,不过为了阐述方便,尽可能原生语法。
编辑器复制粘贴,起码发生以下事情:
- 获取文件对象
- 前端压缩图片文件算法
- 服务器配置七牛cdn
- 返回文件地址
获取文件对象
Clipboard API的Clipboard接口提供了一种读写操作系统剪贴板的方式。
而剪切板事件(copy/paste/cut)是这里涉及的主要的方法。
获取剪切板的内容可以用一个全局api来拿:window.setSelection().toString()
首先是要监听你的paste事件:
假设div##markdownContent
是你要粘贴到区域:
// 二次开发内容
let markdownContent= document.querySelector('#markdownContent');
document.addEventListener('paste', function (event) {
var items = event.clipboardData && event.clipboardData.items;
var file = null;
if (items && items.length) {
// 检索剪切板items
for (var i = 0; i < items.length; i ) {
if (items[i].type.indexOf('image') !== -1) {
file = items[i].getAsFile();
break;
}
}
}
if(file){
// 此时file就是剪切板中的图片文件
console.log('file',file);
// ...
}
});
图片处理
七牛提供了上传压缩的服务。这个服务是付费的。当甲方比较穷,可以考虑在前端把它转小了再传。
原理是拿到file对象之后,new一个FileReader
,以base64的形式读取。
const reader = new FileReader();
reader.readAsDataURL(file);
然而reader加载读取是需要时间的。为了优雅的编码。封装一个方法:
代码语言:javascript复制const getBase64 = () => {
return new Promise((resolve, reject) => {
reader.addEventListener('load', (e) => {
resolve(e.target.result);
});
});
};
接下来就很舒服了。
代码语言:javascript复制let base64 = await getBase64();
压缩
base64是不会压缩图像质量的。但base64是canvas对象很喜欢的格式。
总的思路就是,把你粘贴的图片按照一定的比例,改为最小尺寸。
获取真实宽高
我如果拷贝一个千万级像素的大图。面对一堆base64编码,我又如何知道它的宽高?这时你需要构造一个dom。把它放进去。然后趁它加载完成后,拿下来。
所以接下来的做法和上面如出一辙:
代码语言:javascript复制const getProps = (img) => (new Promise((resolve, reject) => {
img.onload = () => {
resolve({
width: img.width,
height: img.height,
rate: 0.01 * Math.round(100 * img.width / img.height)
})
}
}));
代码语言:javascript复制// 调用
let base64 = await getBase64();
let img = new Image();
img.src = base64;
// 计算比例 根据 宽/高 校正大小
const { rate, width, height } = await getProps(img);
很简单,很简单。
压缩
压缩的业务需求是这样的:
给定一个宽高极限maxWidth
和maxHeight
,如果图片没超过这个极限,就用原图无所谓。但超过了。就得执行一系列计算。
// 初始化canvas
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
// canvas宽高配置。可以从其他文件引入
const canvasConfig = {
maxWidth: 1200,
maxHeight: 600
}
const { maxWidth, maxHeight } = canvasConfig;
const canvas_rate = canvasConfig.maxWidth / canvasConfig.maxHeight;
// 解比例
if(width<maxWidth&&height<maxHeight){
canvas.width = width;
canvas.height = height;
}else{
canvas.width = rate > canvas_rate ? maxWidth : maxHeight * rate;
canvas.height = rate > canvas_rate ? maxWidth / rate : maxHeight;
}
// 核心JS就这个
context.drawImage(img, 0, 0, canvas.width, canvas.height);
let dataURL=canvas.toDataURL('image/jpeg');
这个计算就是解比例。对笔者来说小学六年级难度。听说现在小孩小学二年级奥数就学了。
进入上传流程
canvas最后给到你的也是base64。但传一个blob对后端来说是很友的选择。因此把它转化为blob对象吧
代码语言:javascript复制// 转换base64为二进制文件
function dataURLtoBlob(data) {
var tmp = data.split(',');
tmp[1] = tmp[1].replace(/s/g,'');
var binary = atob(tmp[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'});
}
//上传
let blob=dataURLtoBlob(dataURL);
let fd = new FormData();
fd.append("file", blob);
let res = await this.$post(this.$API.uploadImg, fd});
// res...balabala
你会发现这个dataURLtoBlob
和我代码风格不大一样。因为是我复制来的。涉及的非前端api,看不来,这回就当一回API调用工程师吧。
加水印
实际上你可以告诉用户,你的图片是有版权的。说白了也就是加水印。
代码语言:javascript复制// 添加水印
context.font = "28px Georgia";
context.fillText('♥️一粒小麦 djtao.net', 30, 60);
可以看到效果:
详情将在canvas一文中讲述。
后端处理
如果直接发送到七牛。那后端配合的就是发送一个token。做的事情简单的令人发指。
七牛有一个nodejs的token生成器。你要做的,就是在进入页面时,请求token生成器。
代码语言:javascript复制sudo npm install qiniu -s
代码语言:javascript复制// 主要是图片上传
const qiniu = require('qiniu')
export const getQiniuToken = async (ctx, next) => {
// ak和sk可以在七牛个人中心拿
var accessKey = '。。。';
var secretKey = '。。。';
var mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
var options = {
scope: 'storage',//你的空间名
};
var putPolicy = new qiniu.rs.PutPolicy(options);
// 上传凭证
var uploadToken = putPolicy.uploadToken(mac);
ctx.body = setResponseData(SUCCESS_CODE, SUCCESS_MSG, uploadToken);
}
看到进入页面,就直接拿到token了。
上传
所以直接回到前端。
代码语言:javascript复制//上传
let blob = dataURLtoBlob(dataURL);
let fd = new FormData();
fd.append("file", blob);
fd.append('token',this.token)
let res = await axios.post('http://up-z2.qiniup.com/', fd);
let url='http://markdown.djtao.net/' res.data.key;
你的空间在哪个区域的机房,就post哪个地址。
那么你就拿到url啦!
组装粘贴内容
还记得那串markdown源码吗?组装一下:
代码语言:javascript复制const pasteContnet=`![](${url})`
在markdown编辑器里怎么使用?
两行代码解决:
代码语言:javascript复制var clipboardData = event.clipboardData || window.clipboardData;
event.target.value=event.target.value 'n' pasteContnet;
最后我们来测试一下。
测试图片地址:
原图:
上传后:
其中转换前尺寸3984*3656,大小为2.5M 转换后尺寸为900X600,大小为193k。
需求完成。