前言

这个操作体验倒是不错。今日早读文章由丁香园@蒋璇投稿分享。

@蒋璇, 前端开发攻城狮, 现任职于丁香园. 英语爱好者, 测试驱动开发(TDD)&行为驱动开发(BDD)推崇者. 先专注于 https://github.com/Jiang-Xuan/tuchuang.space 项目的测试驱动开发探索

正文从这开始~~

在网页中上传图片有多重选择.

  • 点击文件上传控件, 选择文件进行上传

  • 从文件浏览器中拖拽文件进行上传

  • 从系统粘贴板中粘贴上传

本篇文章着重介绍最后一种, 也是最方便的上传的方法, Control/Command + v 进行上传, 以及如何使用 selenium 来跨浏览器的自动化测试这个功能.

一般的截图程序, 比如 QQ, 微信, PrintScreen 按钮, 都会将截图以 png 格式放入系统粘贴板, 所以这里讨论 png 格式的粘贴, 而不是其他格式的, 更多的还是给截图程序使用.

浏览器如何获取 Control/Command + v 粘贴的图片数据??

Note: 支持 IE 11, 以及现代浏览器Chrome, Firefox, Safari

现代浏览器在 paste 事件中提供 clipboardData 属性来访问粘贴板中的数据

获取粘贴板中的图片数据可以通过监听 paste 事件来实现:

document.addEventListener('paste', (event) => {

const { items } = event.clipboardData

if (items) {

;[...items].forEach((item) => {

if (item.type.indexOf('image') !== -1) {

// item 的 mime 类型是图片, 说明想要粘贴的是图片数据

// https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem

}

})

}

})

上面的代码中 item 提供 getAsFile 方法来获取粘贴的图片的数据的二进制数据:

file = item.getAsFile()

这里获取到的 file 为 File 的实例, 继承自 Blob, js 中的二进制数据, 你可以直接将 file 上传给后端服务器就可以完成图片的上传:

const formData = newFormData()

formData.append('images', file)

const xhr = newXMLHTTPRequest()

xhr.open('POST', 'https://tuchuang.space/api/v1/images')

xhr.send(formData)

上诉讨论的是现代浏览器的处理, 麻烦的是 IE 11 的处理(IE11 以下的浏览器无法获取粘贴板中的图片数据, 就不用尝试了?), IE 11 支持粘贴板中的图片以 img 标签, src 为 图片的 base64 编码放入设置了 contenteditable 属性的元素之中, 官方来源 Enhanced Rich Editing Experiences in IE11

IE 11 中需要使用 hack 的方法来获取粘贴板中的图片数据

hack 的实例可以去 这里 看下, 要使用 IE 11 浏览器哦, 目前能找到的在线编辑器支持 IE 的也就是 jsfiddle 了. 大部分代码都是 copy 来自 这个 Stack Overflow 问题 中, 思想就是在用户 paste 的时候 focus 一个 设置 contenteditable 属性的 div, 然后从这个 div 中获取数据. 接下来 庖丁解牛, 这里解释的代码的原理和 jsfiddle 中的例子一致, 但是做了一定的优化, 实际的使用可以去 这里 看下, 全部代码如下:

class PasteImage {

/**

* 在获取到用户 paste 的图片数据时的回调函数

* @param {(imageBlobData: Blob) => void} callback

*/

constructor (callback) {

this._callBack = callback

/** @private {boolean} 用户是否正在按下 ctrl 键 */

this._ctrlPressed = false

/** @private {boolean} 用户是否正在按下 command 键, MacOS 系统下 */

this._commandPressed = false

/** @private {HTMLDivElement} 捕获用户粘贴的图片的容器 */

this._pasteCatcher = document.createElement('div')

/** @private {boolean} 是否支持 Native paste 事件 */

this._pasteEventSupport = false

/** @private {HTMLDivElement} 捕获用户粘贴的图片的容器的 ID */

this._pasteCatcherId = `paste-image-${Math.random()}`

this._pasteCatcher.setAttribute('id', this._pasteCatcherId)

this._pasteCatcher.setAttribute('contenteditable', '')

this._pasteCatcher.style.cssText = 'opacity:0;position:fixed;top:0px;left:0px;width:10px;margin-left:-20px;'

/**

* 处理页面按键按下

* @private

* @type {(event: KeyboardEvent) => void}

*/

this._handleOnKeyDown = this._handleOnKeyDown.bind(this)

/**

* 处理页面按键释放

* @private

* @type {(event: KeyboardEvent) => void}

*/

this._handleOnKeyUp = this._handleOnKeyUp.bind(this)

/**

* 处理页面的 paste 事件

* @private

* @type {(event: ClipboardEvent) => void}

*/

this._handleOnPaste = this._handleOnPaste.bind(this)

const observer = new MutationObserver((mutations) => {

mutations.forEach((mutation) => {

if (this._pasteEventSupport || this._ctrlPressed === false || mutation.type !== 'childList') {

return

}

if (mutation.addedNodes.length === 1) {

if (mutation.addedNodes[0].src !== undefined) {

this._pasteCreateImage(mutation.addedNodes[0].src)

}

}

})

})

observer.observe(this._pasteCatcher, {

childList: true,

attributes: true,

characterData: true

})

}

/**

* 处理非标准的 paste 事件, 从 image 标签中获取数据

* 目前支持的浏览器中只有 IE 11 不支持标准的 paste 事件

* IE 11 中粘贴的图片的格式为 [data url](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs)

*

* atob('MTIz') // 123

*

* Example: data:image/png;base64,MTIz

*

* @private

* @param {string} source image 标签的 src 属性

*/

_pasteCreateImage (source) {

const base64String = source.split(',')[1]

const buffer = base64js.toByteArray(base64String)

const uint8 = new Uint8Array(buffer)

const pngBlob = new Blob([uint8], { type: 'image/png' })

this._callBack(pngBlob)

}

/**

* 处理页面按键按下

* @private

* @param {KeyboardEvent} event

*/

_handleOnKeyDown (event) {

const { keyCode } = event

console.log(event)

if (keyCode === 17 || event.metaKey || event.ctrlKey) {

if (this._ctrlPressed === false) {

this._ctrlPressed = true

}

}

if (keyCode === 86) {

if (document.activeElement !== null && document.activeElement.type === 'text') {

// 允许用户拷贝文字进入输入框

return false

}

if (this._ctrlPressed === true) {

this._pasteCatcher.focus()

}

}

}

/**

* 处理页面按下释放

* @private

* @param {KeyboardEvent} event

*/

_handleOnKeyUp (event) {

// ctrl

if (event.ctrlKey && this._ctrlPressed === true) {

this._ctrlPressed = false

}

// command

if (event.metaKey && this._commandPressed === true) {

this._commandPressed = false

this._ctrlPressed = false

}

}

/**

* 处理页面的 paste 事件

* @private

* @param {ClipboardEvent} event

*/

_handleOnPaste (event) {

this._pasteCatcher.innerHTML = ''

if (event.clipboardData) {

const { items } = event.clipboardData

if (items) {

this._pasteEventSupport = true

;[...items].forEach((item) => {

if (item.type.indexOf('image') !== -1) {

console.log(item)

const blob = item.getAsFile()

this._callBack(blob)

}

})

}

}

}

/**

* 监听事件, 将 pasteCatcher 放入 body 中

* @public

*/

install () {

document.body.appendChild(this._pasteCatcher)

document.addEventListener('keydown', this._handleOnKeyDown)

document.addEventListener('keyup', this._handleOnKeyUp)

document.addEventListener('paste', this._handleOnPaste)

}

uninstall () {

document.body.removeChild(this._pasteCatcher)

document.removeEventListener('keydown', this._handleOnKeyDown)

document.removeEventListener('keyup', this._handleOnKeyUp)

document.removeEventListener('paste', this._handleOnPaste)

}

}

使用方法:

const pasteImage = new PasteImage((blob) => {

// blob 就是获取到的图片的二进制数据

})

pasteImage.install()

// 如果想要停止监听 paste, 调用 pasteImage.uninstall()

constructor 构造函数

构造函数接受一个回调函数作为在接收到数据的时候的回调.

this._callBack = callback

_ctrlPressed 判断用户是否按下 control 按键(Windows 下粘贴组合键为 Control + v), _commandPressed 判断用户是否按下 command 按键(Macos 下粘贴组合键为 command + v, Macos 没有 IE 11, 其实 Firefox 22 以下也不支持标准的 paste 方法获取图片数据?, 不过也可以忽略了) .

/** @private {boolean} 用户是否正在按下 ctrl 键 */

this._ctrlPressed = false

/** @private {boolean} 用户是否正在按下 command 键, MacOS 系统下 */

this._commandPressed = false

_pasteEventSupprt 判断浏览器是否支持通过标准的 paste 事件获取数据.

/** @private {boolean} 是否支持 Native paste 事件 */

this._pasteEventSupport = false

接下来创建一个 div, 用来在不支持标准的 paste 事件获取数据的浏览器中捕获用户粘贴操作(其实就是 IE 11), 给这个 div 设置 id 属性, 然后设置其的 contenteditable 属性, 给这个 div 设置 css, 让其不会显示在用户的屏幕上.

/** @private {HTMLDivElement} 捕获用户粘贴的图片的容器 */

this._pasteCatcher = document.createElement('div')

/** @private {HTMLDivElement} 捕获用户粘贴的图片的容器的 ID */

this._pasteCatcherId = `paste-image-${Math.random()}`

this._pasteCatcher.setAttribute('id', this._pasteCatcherId)

this._pasteCatcher.setAttribute('contenteditable', '')

this._pasteCatcher.style.cssText = 'opacity:0;position:fixed;top:0px;left:0px;width:10px;margin-left:-20px;'

接下来是绑定页面上的几个事件监听器的 this 指向, 包括监听用户按下按键, 释放按键, 和 paste 事件的监听器.

/**

* 处理页面按键按下

* @private

* @type {(event: KeyboardEvent) => void}

*/

this._handleOnKeyDown = this._handleOnKeyDown.bind(this)

/**

* 处理页面按键释放

* @private

* @type {(event: KeyboardEvent) => void}

*/

this._handleOnKeyUp = this._handleOnKeyUp.bind(this)

/**

* 处理页面的 paste 事件

* @private

* @type {(event: ClipboardEvent) => void}

*/

this._handleOnPaste = this._handleOnPaste.bind(this)

为了在 IE 11 上获取到用户粘贴到上面的 _pasteCatcher 容器之中的内容, 需要监听这个 DOM 的子元素的变动, 通过 MutationObserver 来实现, 如果支持标准的 paste 事件获取数据, 或者是 control 没有被按下, 或者是不是子元素的变化, 则不处理. 否则找到被添加的元素, 如果是图片的粘贴, 在 IE11 中将是通过 img 标签以 data url 为 src, data url 为 image base64 编码, 将这个 data url 取出来传递给 _pasteCreateImage 函数.

const observer = new MutationObserver((mutations) => {

mutations.forEach((mutation) => {

if (this._pasteEventSupport || this._ctrlPressed === false || mutation.type !== 'childList') {

return

}

if (mutation.addedNodes.length === 1) {

if (mutation.addedNodes[0].src !== undefined) {

this._pasteCreateImage(mutation.addedNodes[0].src)

}

}

})

})

observer.observe(this._pasteCatcher, {

childList: true,

attributes: true,

characterData: true

})

_pasteCreateImage 方法

接收一个参数, 就是 图片的 data url, 比如 data:image/png;base64,MTIz(MTIz 是 123 的 base64 编码)

将图片的 base64 编码数据从 data url 找出并提取出来.

const base64String = source.split(',')[1]

将 base64 转成二进制数据, 这里用到的是 base64-js, 可以将 base64 编码转换成二进制数据, 在 nodejs 中, 这种转换是内置的.

const buffer = base64js.toByteArray(base64String).buffer

然后用这个 buffer 创建 mimetype 是 image/png 的 Blob 对象

const pngBlob = newBlob([buffer], { type: 'image/png'})

成功的拿到了需要的数据, 调用回调将数据传递出去

this._callBack(pngBlob)

_handleOnKeyDown 方法

这是一个按键按下监听器, 在键盘被按下的时候触发该函数.

从 event 参数中获取 keycode

const{ keyCode } = event

如果 keycode 是 17 或者是 event.metaKey, event.ctrlKey 成立, 则是用户按下了 control 修饰键.

if (keyCode === 17 || event.metaKey || event.ctrlKey) {

if (this._ctrlPressed === false) {

this._ctrlPressed = true

}

}

如果 keycode 是 86, 86 是 v 的 keycode. document.activeElement 获取当前被聚焦的元素 , 如果被聚焦的是一个 type 是 text 的 input 输入框, 用户是想将文字拷贝进输入框, 而不是粘贴图片.

if (keyCode === 86) {

if (document.activeElement !== null && document.activeElement.type === 'text') {

// 允许用户拷贝文字进入输入框

return false

}

if (this._ctrlPressed === true) {

this._pasteCatcher.focus()

}

}

在 _pasteCatcher 元素被 focus 之后, 用户 ctrl+v 的数据就会粘贴进 _pasteCatcher 元素内部中:

这会触发 _pasteCacher 的 MutationObserver 的回调

const observer = new MutationObserver((mutations) => {

mutations.forEach((mutation) => {

if (this._pasteEventSupport || this._ctrlPressed === false || mutation.type !== 'childList') {

return

}

if (mutation.addedNodes.length === 1) {

if (mutation.addedNodes[0].src !== undefined) {

this._pasteCreateImage(mutation.addedNodes[0].src)

}

}

})

})

如果浏览器原生支持标准的 paste 事件, 或者是 control 按键没有被按下, 或者这不是一个 childList 类型的 mutation, 不处理. 否则判断 mutation 的对否有添加的节点, 然后判断第一个被添加的节点的 src 属性是否存在, 因为图片的粘贴必定是 img 标签, 并且有 src 属性, 这个时候就可以判断出用户粘贴的是一张图片, 将获得到的图片的 data url 传递给处理函数 pasteCreateImage.

监听 _pasteCacher 的变化的调用, 其实可以只监听 childList.

observer.observe(this._pasteCatcher, {

  childList: true,

  attributes: true,

  characterData: true

})

跨浏览器自动化测试

CI 服务为 Github 提供的 Github Actions

测试的浏览器为: IE 11, Chrome latest(Github Actions 提供的 Chrome), Firefox latest(Github Actions 提供的 Firefox)

e2e 测试的工具为 selenium

为什么是 selenium?

puppeteer, cypress 只支持 chromium 系列浏览器, 无法达成跨浏览器测试需求 passed

karma 只能在浏览器内部执行代码, 无法操作操作系统的剪切板, passed

测试的步骤如下:

准备一张测试的 png 图片, 计算这张图片的 bitmap, 这里是用 jimp 来计算出测试图片的 bitmap

image.bitmap.data; // a Buffer of the raw bitmap data

向操作系统的剪切板写入第一步准备的图片

访问 tuchuang.space ctrl + v 快捷键粘贴图片

应该页面发起请求, 并且传入的是一张 png 图片, 并且图片的 bitmap 和第一步准备的图片的 bitmap 一致

第一步, 准备测试图片, 计算图片的 bitmap

第一步和第二步被封装到了一个单独的 npm 包中 copy-logo-to-clipboard

测试图片为 tuchuang.space 的 压缩版 logo, 为了测试方便, 压缩到了只有4个像素, 像素的 rgba 16进制的值为:

第一个像素的 rgba 值: rgba(124, 158, 181, 253) 第二个像素的 rgba 值: rgba(139, 137, 165, 253) 第三个像素的 rgba 值: rgba(243, 188, 110, 253) 第四个像素的 rgba 值: rgba(219, 89, 89, 253)

第二步, 将图片写入操作系统

处理起来最麻烦的一步

支持 Windows, Macos

nodejs 中没有一个很好的办法操作操作系统的剪切板, Windows 操作系统下可以使用 C# 加上 .net 框架和操作系统的剪切板交互, 可以看下我的尝试 github.com/Jiang-Xuan/… github.com/Jiang-Xuan/… 编写代码使用的平台是 Macos, 所以还要处理 Mac 平台的剪切板的交互, Swift 太难了. 最后我放弃了, 转而使用 electron 提供的 api 来处理 github.com/Jiang-Xuan/… electron 提供了 方法 来将图片写入操作系统的剪切板.

// Modules to control application life and create native browser window

const{ app, clipboard, nativeImage } = require('electron')

const path = require('path')

console.log('main.js')

const fooImage = nativeImage.createFromPath(path.resolve(__dirname, './logo.png'))

// This method will be called when Electron has finished

// initialization and is ready to create browser windows.

// Some APIs can only be used after this event occurs.

app.on('ready', () => {

console.log('ready')

clipboard.writeImage(fooImage)

console.log(clipboard.readImage())

app.quit()

})

打包一个 electron 应用来实现, electron 的应用打包出来都比较大, 但是在没有更好的办法的情况下只能这样, 分发一个 electron 应用来实现跨平台的操作系统的剪切板操作. 暴露出去一个 copyLogoToClip 方法, 使用方法为:

const{ copyLogoToClip } = require('copy-logo-to-clipboard')

// 写入操作系统

await copyLogoToClip()

这里说一个小故事 在刚开始的时候我并没有给这个模块写测试用例, 在我实际在 tuchuang.space 项目中写测试用例的时候我发现在读取出来的图片和写入的图片的 bitmap 并不一致, 这个时候我不确定是哪一部分出的问题了, 到底是 copy-logo-to-clipboard 在向系统剪切板写入图片的时候修改了图片的 bitmap, 还是浏览器在读取操作系统的剪切板的 bitmap 的时候改变了图片的 bitmap? 太相信浏览器导致我一度怀疑是 electron 修改了图片的 bitmap, 可是最后却发现了是某些浏览器修改了图片的 bitmap, 如果我在刚开始的时候对 copy-logo-to-clipboard 写了测试用例, 我就有理由相信是浏览器出了问题, 所以后续我对 copy-logo-to-clipboard 写了 测试用例 来保证这个模块是正确的

第三步, 按下 ctrl+v

在按下 ctrl+v 这一步也有坑, 在 Macos chrome 上, 你会发现无论是 control+v 还是 command+v 都无法执行粘贴操作, 辗转多处, 在 Stack Overflow 上面发现了 解决办法, 就是按下 Shift + Insert

另一个需要注意的点是在 IE 11 下, 我们做了特殊的粘贴图片的处理, 如果我们用程序按下 ctrl+v 你会发现无法粘贴图片, 是因为程序的操作太快了, 没有给我们聚焦 _pasteCatcher 的机会, 但是实际的用户操作的时候并没有这么快, 所以特殊处理一下 IE 11 下的 ctrl+v 的按下的时机, 以更符合实际的用户操作

const controlKeyDown = driver.actions().keyDown(Key.CONTROL)

const vKeyDown = driver.actions().keyDown('v')

const vKeyUp = driver.actions().keyUp('v')

const controlKeyUp = driver.actions().keyUp(Key.CONTROL)

awaitnewPromise((resolve) => setTimeout(resolve, 2000))

await body.click()

await controlKeyDown.perform()

awaitnewPromise((resolve) => setTimeout(resolve, 500))

await vKeyDown.perform()

awaitnewPromise((resolve) => setTimeout(resolve, 500))

await vKeyUp.perform()

awaitnewPromise((resolve) => setTimeout(resolve, 500))

await controlKeyUp.perform()

页面发起请求, 并且传入的是一张 png 图数据, 并且图片的 bitmap 和第一步准备的图片的 bitmap 一致

不想让页面真正的向后端发起请求, 但是却没有找到一种可以拦截 selenium 操作的浏览器的请求, 在 puppeteer 中可以通过监听 page.on('request') 事件来拦截和 mock 请求

// 来自: https://pptr.dev/#?product=Puppeteer&version=v2.0.0&show=api-pagesetrequestinterceptionvalue

const puppeteer = require('puppeteer');

puppeteer.launch().then(async browser => {

const page = await browser.newPage();

await page.setRequestInterception(true);

page.on('request', interceptedRequest => {

if(interceptedRequest.url().endsWith('.png') || interceptedRequest.url().endsWith('.jpg'))

interceptedRequest.abort();

else

interceptedRequest.continue();

});

await page.goto('https://example.com');

await browser.close();

});

为了达到类似 puppeteer 的这种功能, 可以 mock 一个服务器, 然后在 selenium 环境中请求 mock 的服务器, 我手动实现了一个 mock-server, 提供的功能仅仅满足该测试的需求, 详情可以去项目仓库看细节(建议看测试用例来了解, 没有文档的情况下测试用例就是最好的文档). 配置想要返回的请求, 启动 mock 服务器

mockServer.config.configResponse({

body: {

images: {

'image_from_clipboard.png': {

mimetype: 'image/png',

md5: '637e2ee416a2de90cf6e76b6f4cc8c89',

filename: 'test-test.png',

ossPath: 'http://example.com/test-test.png',

cdnPath: 'https://i.tuchuang.space/test.png',

deleteKey: '2436b48115486de952296f2b5295aeb90d284761278661102e7dda990c3f67022133080fb1bcd99d7f94678a991c57f1'

}

}

}

})

await mockServer.start()

在请求执行完毕之后, 在 mock 服务器接收到的请求中进行搜索, 找到需要的请求, 然后进行判断请求服务接收到的参数是否正常

// assert https://github.com/Jiang-Xuan/tuchuang.space/issues/36#issuecomment-566868929

awaitnewPromise((resolve) => setTimeout(resolve, 2000))

const requests = mockServer.search({ path: '/api/v1/images'})

awaitnewPromise((resolve) => setTimeout(resolve, 2000))

const outputLogoJimp = await jimp.read(requests[0].files[0].buffer)

const logoBitmap = await getLogoBitmap()

if(forBrowser === 'chrome') {

expect(md5(outputLogoJimp.bitmap.data)).toEqual(md5(logoBitmap))

} else{

expect(outputLogoJimp.getMIME()).toEqual('image/png')

}

你可能注意到了, 对于 bitmap 的一致判定, 我只判断了 chrome 浏览器, 这是一个我目前也都没有找到具体原因的地方, 接下来用一个段落详解原因

为什么只对 chrome 浏览器判断 bitmap

本段只针对 Windows 平台, 在 macos 平台下, Firefox 是可以正常的读取出在粘贴板中的图片的 bitmap

在刚开始写测试的时候, 我笃定浏览器可以正常的读取出在粘贴板中的图片的 bitmap, 但是经过后续的测试发现只有 chrome 能正确的读取图片的 bitmap, IE 11(hack 方式处理), Firefox(标准的方法) 均无法保证读取出来的图片的 bitmap 和最初的图片的 bitmap 完全一致, 虽然有时肉眼并无法分辨出图片的细节. 最明显的一个问题是透明通道丢失了, 初以为是 IE 11 在读取的时候做了处理, 后来发现 Firefox 也是如此, 并且同一张图片, 在 IE11和 Firefox 中的结果一致, 所以做出了以下猜测:

IE 11 和 Firefox 都是调用 Windows 提供的某一个接口, 是这个接口读取操作系统粘贴板的时候做了一些操作, 这也能解释为什么 Firefox 在 macos 平台上面是正常的

Chrome 为什么是正常的? chrome 调用了不同的接口, 或者是自己实现了接口

所以最后只针对 chrome 做了 bitmap 的对比, 而在 IE11 和 Firefox 上则只判断接收到了一张 png 图片 expect(outputLogoJimp.getMIME()).toEqual('image/png')

你可以使用官方的测试用例来测试一下不同的浏览器 w3c-test.org/clipboard-a…

总结

IE 11 获取粘贴板中的图片需要 hack 的方式

用 TDD 的方式来测试这个功能其实非常复杂, 因为涉及到了操作系统, 而操作系统又是一个很难以 mock 的对象, 所以只能操作真正的操作系统, Macos 平台和 Windows 平台提供的接口不一致, 使用 electron 可以帮助抹平平台差异.

只有 chrome 保证了读取出来的图片的 bitmap 是和原始的图片的 bitmap 完全一致, 其他浏览器均不能保证(在 Windows 下, Macos 下 chrome, Firefox 均可以保证, Safari 没有测试). 所以尽量不要测试图片的 bitmap 是否一致, 测试是一张图片就够了.

测试步骤:

?准备测试图片, 计算图片的 bitmap.

?将图片写入操作系统. 注意这里必须对这个操作做测试, 以保证写入的和读取出来的图片数据一致

⌨︎按下 ctrl+v. Macos 的 chrome 下按下的是 shift+insert

✅页面发起请求, 并且传入的是一张 png 图数据, 并且图片的 bitmap 和第一步准备的图片的 bitmap 一致. selenium 测试没有找到监听请求的方法, 可以 mock 一个 server. 只有 chrome 需要测试 bitmap, 其余浏览器测试接收到的是一张 png 图片就可

❓留下的问题

到底是什么原因导致的 Firefox 和 IE11 在 Windows 下无法读取出一致的图片的 bitmap ?

关于本文 作者:Jiang-Xuan 原文:https://juejin.im/post/5e0d965ef265da5d597e0fd5

你可能还喜欢看

【译】WebAssembly 1.0成为W3C推荐标准,也是在浏览器中运行的第四种语言

【第1537期】Fusion Next 之 Upload 上传组件设计思路

【第1262期】Jenkins打造强大的前端自动化工作流

js粘贴板为什么获取不到图片信息_【第1829期】复制黏贴上传图片和跨浏览器自动化测试...相关推荐

  1. js粘贴板为什么获取不到图片信息_图床+typora,告别markdown中关于图片的困惑

    在上一篇文章中向大家介紹了几款软件,这篇文章主要分享一下markdown编辑器typora软件如何使用图床,快速的将图片加载到markdown文档中. 图床: 指存储图片的服务器,将图片上传到服务器上 ...

  2. js粘贴板为什么获取不到图片信息_JavaScript 学习笔记(3):图片库

    本文使用 Zhihu On VSCode 创作并发布 1. 为什么要使用图片库 可以把所有的图片都放到一个网页里,不过当图片过多时,会导致网页体积过大. 因此,为每张图片分别创建一个网页的解决方案. ...

  3. js 如何通过代码复制内容到粘贴板

    js 如何通过代码复制内容到粘贴板 功能描述 js 如何通过代码复制内容到粘贴板 功能实现 1.document.execCommand("Copy") 代码如下,复制即可: le ...

  4. linux vim内容复制粘贴板,Vim 使用系统粘贴板复制粘贴

    习惯了Windows下面的复制,粘帖快捷键.转到vim编辑器会发现这两个键没什么作用,其实vim是使用ctrl+shift+c,ctrl+shift+v复制粘帖的.但是使用这些快捷键只能在一定屏幕范围 ...

  5. 同步ubuntu粘贴板和windows系统粘贴板

    通过以下几步操作即可实现ubuntu粘贴板和windows粘贴板的同步功能,更加便于我们进行粘贴和复制等功能. #背景:Ubuntu运行在VMware虚拟机中,VMware运行在Windows7操作系 ...

  6. 【js】vue项目中实现点击复制过滤条件,获取并处理粘贴板内容

    前情提要 有这样一个需求:每次重复选过滤条件太麻烦啦,需要一个可以复制过滤条件的功能!过滤条件类似下图. 主要步骤 第一步:复制工具的选取.这里我选用的是原生的Document.execCommand ...

  7. js html table转excel文件 js获取(复制 / 粘贴板)的内容js复制table粘贴到excel中

    js html table转excel文件 参考资料: 70行代码实现vue+sheetJs导出excel功能 sheetJs的git项目代码 代码太长建议直接粘贴复制 <!DOCTYPE ht ...

  8. JS获取粘贴板中的图片进行展示和上传

    目前有一个需求,需要在页面中获取QQ.微信等软件的截图上传到服务器,为了用户体验,不能让用户主动上传,提供给用户方法,在web页面使用粘贴快捷键,就可以粘贴到页面,然后点击发送进行上传.而且用户如果粘 ...

  9. Web js复制文本到粘贴板

    Web js复制文本到粘贴板 一.简述 记--简单用js实现将元素的文本内容复制到粘贴板. 二.效果 三.代码 <!DOCTYPE html> <html><head&g ...

最新文章

  1. nginx 在负载均衡中 的配置 以获取真实IP
  2. spring整合junit测试
  3. python可以写安卓应用吗_python可以编写android程序吗?
  4. Storm精华问答 | Kafka在Storm中的角色是什么?
  5. visual studio运行时库MT、MTd、MD、MDd 的区别
  6. booststraping
  7. JSON日期时间的处理
  8. boost asio生成lib
  9. ubuntu compiz 不能启动
  10. 华为KubeEdge在边缘计算的实践
  11. python xlsx表格最大行最大列
  12. 如何快速新建文件和文件夹
  13. 用C++开发STM32程序
  14. PF_RING 6.0.2发布
  15. 在不支持AirDrop的Mac上开启和使用AirDrop的方法
  16. 查看dg主备库同步情况
  17. mysql的decimal保留两位小数_C#中的decimal怎么保留两位小数
  18. keepalived的健康检查方式
  19. 提示 STOP:c000021a unknown hard error
  20. msn.com邮箱注册新法

热门文章

  1. Angular 内容投影 II
  2. Ubuntu 搭建 GitLab 笔记 ***
  3. openfire(一):使用idea编译openfire4.2.3源码
  4. WCF系列教程之WCF客户端调用服务
  5. 一句DOS命令搞定文件合并
  6. 关于OC-省市区习题
  7. Light OJ 1406 Assassin`s Creed 减少国家DP+支撑点甚至通缩+最小路径覆盖
  8. BigDecimal.setScale 处理java小数点
  9. 简明Vim练级攻略(初学者)
  10. rails3系统架构