专栏首页一Li小麦复制粘贴那些事

复制粘贴那些事

需求

这篇公众号文章是用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格式的链接地址。这是其他笔记应用没有的。

![](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加载读取是需要时间的。为了优雅的编码。封装一个方法:

const getBase64 = () => {
    return new Promise((resolve, reject) => {
        reader.addEventListener('load', (e) => {
            resolve(e.target.result);
        });
    });
};

接下来就很舒服了。

let base64 = await getBase64();

压缩

base64是不会压缩图像质量的。但base64是canvas对象很喜欢的格式。

总的思路就是,把你粘贴的图片按照一定的比例,改为最小尺寸。

获取真实宽高

我如果拷贝一个千万级像素的大图。面对一堆base64编码,我又如何知道它的宽高?这时你需要构造一个dom。把它放进去。然后趁它加载完成后,拿下来。

所以接下来的做法和上面如出一辙:

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)
        })
    }
}));
// 调用
let base64 = await getBase64();
let img = new Image();
img.src = base64;

// 计算比例 根据 宽/高 校正大小
const { rate, width, height } = await getProps(img);

很简单,很简单。

压缩

压缩的业务需求是这样的:

给定一个宽高极限maxWidthmaxHeight,如果图片没超过这个极限,就用原图无所谓。但超过了。就得执行一系列计算。

// 初始化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对象吧

// 转换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调用工程师吧。

加水印

实际上你可以告诉用户,你的图片是有版权的。说白了也就是加水印。

// 添加水印
context.font = "28px Georgia";
context.fillText('♥️一粒小麦 djtao.net', 30, 60);

可以看到效果:

详情将在canvas一文中讲述。

后端处理

如果直接发送到七牛。那后端配合的就是发送一个token。做的事情简单的令人发指。

七牛有一个nodejs的token生成器。你要做的,就是在进入页面时,请求token生成器。

sudo npm install qiniu -s
// 主要是图片上传
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了。

上传

所以直接回到前端。

//上传
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源码吗?组装一下:

const pasteContnet=`![](${url})`

在markdown编辑器里怎么使用?

两行代码解决:

var clipboardData = event.clipboardData || window.clipboardData;
event.target.value=event.target.value+'\n'+pasteContnet;

最后我们来测试一下。

测试图片地址:

原图:

上传后:

其中转换前尺寸3984*3656,大小为2.5M 转换后尺寸为900X600,大小为193k。

需求完成。

本文分享自微信公众号 - 一Li小麦(gh_c88159ec1309),作者:一li小麦

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-08-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 小程序(2):云开发

    所谓serverless就是无后台开发。通俗地说就是踢开后端闹革命。只需要一个前端就可以操作数据库小程序云开发就是这个概念的尝鲜者。云开发也是小程序近年最大的改...

    一粒小麦
  • nodejs基础和核心api

    前端关心的是浏览器的bom和dom。node关注的是操作系统(fs,net,database,buffer,event,os)

    一粒小麦
  • react全家桶之router使用

    然而这样确实有他的道理。假使上课的有1000人,那应该通宵开发出来。而在没有那么多人的情况下,也许还能在用几年。

    一粒小麦
  • 如何遍历JavaScript中对象属性

    在2016年6月发布的ECMAScript 2016的同一时期,令JavaScript开发人员开心的是知道另一组很棒的提案已经达到了第4阶段(完成)。

    疯狂的技术宅
  • FRM知识点结构

    这是我对FRM里面Learing Object的整理,一共有66个大的Learning Object。我把相关的整理在了一起,方便大家对学到的知识有个清晰的定位

    rocket
  • rabbitmq搭建集群踩坑记

    三台节点启动之后,想要使用s146作为集群主节点,在s151上将该节点加入s146集群时,报如下错误。

    我是李超人
  • 彻底理解Java IO

    谈到IO,我们会想到从磁盘读取的文件IO,网络请求的Socket IO,还有可能我们不怎么常用的跨进程通信的管道IO...... 这些在Java中都被抽象为“...

    三好码农
  • CentOS 7 安装 JAVA环境(JDK 1.8)

    地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-213315...

    游魂
  • 【MySQL】Mysql5.7.21 传统复制切换到gtid复制遇到的一个现象

    系统:centos7 主库 M:192.168.16.12:3306 从库 S:192.168.16.15:3306 主从复制:传统复制

    用户5522200
  • JS原生引用类型解析1-Object类型

    (注1:如果有问题欢迎留言探讨,一起学习!转载请注明出处,喜欢可以点个赞哦!) (注2:更多内容请查看我的目录。)

    love丁酥酥

扫码关注云+社区

领取腾讯云代金券