从微信H5点击保存图片说起-微信图片下载

微信H5里保存图片,大都是通过长按操作,弹起actionsheet保存照片。但我们的部分用户是老年群体,不知道怎么进行“长按”操作。考虑到这个因素,产品要求实现点击保存按钮来保存图片。

不难想,你会立马蹦出这几个方案:

方案一 使用HTML5的download属性

浏览器里,如果有点击下载图片的需求,可以使用download属性,如:

<a href="me.png" download="me"></a>

但在使用前,需要考虑下两个问题:跨域问题和兼容问题。

1.跨域问题

download只对同源资源有效。MDN上关于这一点有说明,详见:

<a>: The Anchor element​developer.mozilla.org/en-US/docs/Web/HTML/Element/a正在上传…重新上传取消

想解决这个问题,也比较简单,搭个代理转发一下即可。

2.兼容性问题

caniuse上显示的download属性兼容情况如下:

图中显示QQ浏览器是不支持的,而微信内置的浏览器和QQ浏览器使用的是一样的内核,即X5内核,那基本可推断出微信也是不支持download属性的。

但我又有一点不甘心,决定亲自实践看是否支持download属性。

var hasDownloadAttribute = 'download' in document.createElement('a')

将该脚本加在网页里,并在微信环境打开,运行结果居然是"true"。

只有两种推论:

1)当前设备的微信内置浏览器不是X5内核

2)当前设备微信内置浏览器是X5内核,微信已经开始支持download属性了。

那怎么验证当前设备微信内置浏览器是不是X5内核呢?在微信里打开

X5内核调试专用页​debugx5.qq.com

,结果显示当前设备的微信内置浏览器不是X5内核。嗯,能解释得通了。

而我当前设备上的微信内置浏览器是什么呢?通过navigator.userAgent查看,当前设备上的微信内置的浏览器是Chrome 78.0,内核是Blink。

但是不是应用了download属性就可以实现微信H5点击图片下载了?

太天真。抛开兼容性问题,实践下来,还是无法实现点击保存图片。保存图片需要系统相册的权限,只能由app本身来做,不论你是Webview还是微信内置浏览器的网页,如果没有跟微信通信,调用app本身保存图片的能力,无法实现点击保存图片。

方案二 使用download.js

download兼容性不好是吧?那我用第三方库download.js可以了吧?

rndme/download​github.com/rndme/download

示例代码如下:

import axios from 'axios';
import download from 'downloadjs';
 
axios.get(someUrl, { responseType: 'blob'}).then(res => {
  download(res.data, "awesome.png", "image/png");
})

先讲结论,同方案1一样,只是解决了兼容性问题,但无法调起微信app保存图片的能力。

不work没关系,可以顺便来看看download.js怎么兼容的。

download.js的源码,我浓缩了一下,用伪代码表示是这样。

const saver = (url, mimeType) => {
  // 如果支持download属性
  if ('download' in anchor) {
   anchor.href = url;
   anchor.style.display = 'none';
   document.body.appendChild(anchor);
   setTimeout(() => {
     anchor.click();
     document.body.removeChild(anchor);
     setTimeout(() => {
      // 释放通过createObjectURL创建的URL
       window.URL.revokeObjectUrl(blob);
     }, 250)
   }, 100)
   return;
  } 
  
  // 兼容不支持download属性的部分Safari浏览器
  if (/(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(navigator.userAgent)) {
    if (/^data:/.test(url) { url = 'data:' + url.replace(/^data:([\w\/\-\+]+)/, defaultMime) }
    if (!window.open(url)) { 
      location.href = url;
    }
    return
  }

  // 利用iframe来实现下载。需要注意的是,如果浏览器支持预览该文件格式,则不能实现下载。
  const f = document.createElement('iframe');
  document.body.appendChild(f);
  f.src = url;
  setTimeout(() => {
    document.body.removeChild(f);
  }, 300)
}

// Case 1: 如果支持Blob和URL
if (window.URL) {
  // 创建指向blob对象的URL
  const url = window.URL.createObjectURL(blob);
  saver(url);
  return;
}

// Case 2: 如果支持Blob和msSaveBlob,但不支持URL。
if (navigator.msSaveBlob) {
  navigator.msSaveBlob(dataUrlToBlob(payload), filename);
  return;
} 

// Case 3: 如果支持Blob,但不支持URL和msSaveBlob
// 将blob转成base64编码的数据
const reader = new FileReader()
reader.readAsDataURL(blob)
reader.onloadend = function () {
  const base64data = reader.result
  saver(base64data);
}

// Case 4: 如果Blob和URL都不支持
if (!window.Blob && !window.URL) {
  const base64Data = window.btoa(blob);
  const url = `data:${mimeType};base64,${base64Data}`; 
  saver(url);
}

上面有个比较关键的函数:dataUrlToBlob。它主要是将dataUrl转成Blob对象,保证msSaveBlob接收的参数是Blob对象实例。

function dataUrlToBlob (strUrl) {
   const parts = strUrl.split(/[:;,]/)
   const type = parts[1]
   const indexDecoder = strUrl.indexOf('charset') > 0 ? 3 : 2
   // 如果字符串是base64编码,解码方法使用atob;否则使用decodeURIComponent。
   const decoder = parts[indexDecoder] == 'base64' ? atob : decodeURIComponent
   // 解码
   const binData = decoder(parts.pop())
   const mx = binData.length
   let i = 0
   // 创建数组长度为mx的数组,其中数组元素都是0
   const uiArr = new Uint8Array(mx)
   // 将解码后的数据进行unicode编码
   for (i; i < mx; ++i) { uiArr[i] = binData.charCodeAt(i) }
   return new Blob([uiArr], { type })
}

总结一下就是:先看是否支持Blob和URL,再看是否支持download属性。

虽然方案二是不work的,但解决浏览器里H5相关资源的下载可以使用download.js。

方案三 使用微信js-sdk

方案一和方案二不能下载图片的原因,都在于没有与原生app进行通信,调用原生app下载图片的能力。那引入微信的js-sdk总可以了吧?

先说结论,就该应用场景来说,不支持。

需要下载的图片资源来源于管理系统后台,而不是用户。

而微信js-sdk需要用户先从本地系统相册上传图片到微信图片服务器,才能从图片服务器下载图片到本地。文档如下:

概述 | 微信开放文档​developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#20

文档里也有说明,上传图片的有效期是3天,所以为了长期为用户提供下载服务,还需要下载图片到自己的服务器上。下载图片需要前后调用三个API:wx.chooseImage、wx.uploadImage和wx.downloadImage。

wx.chooseImage({
  count: 1, // 默认9
  sizeType: ['original'],
  sourceType: ['album', 'camera'],
  success (res) {
    const localId = res.localIds[0]
    // 选择图片成功,上传到微信图片服务器
    wx.uploadImage({
      localId, // 需要上传的图片的本地ID,由chooseImage接口获得
      isShowProgressTips: 1, // 默认为1,显示进度提示
      success (res) {
        const serverId = res.serverId // 返回图片的服务器端ID
        wx.downloadImage({
          serverId, // 需要下载的图片的服务器端ID,由uploadImage接口获得
          isShowProgressTips: 1, // 默认为1,显示进度提示
          success (res) {
            this.localId = res.localId
          }
        })
      }
    })
  }
})

方案四 js手动实现长按事件,唤起actionsheet

已知的是用户长按图片,能唤起actionsheet,那肯定app和微信H5之间是有通信的,对touchstart和touchend事件有监听。那既然点击事件无法唤起微信保存图片的actionsheet,我能不能用js模拟用户,手动实现长按事件呢?

onClick的回调里绑定longPress函数。

function longPress () {
  const target = document.getElementById('share-img')
  const startEvent = createTouchEvent(target, 'touchstart')
  target.dispatchEvent(startEvent)
  // 5s后触发touchend
  setTimeout(() => {
    const endEvent = createTouchEvent(target, 'touchend')
    target.dispatchEvent(endEvent)
  }, 5000)
}

function createTouchEvent (touchTarget, type) {
  const event = document.createEvent('UIEvent')
  event.initUIEvent(type, true, true, window, 1)

  const touches = [{ target: touchTarget }]

  Object.defineProperties(event, {
    changedTouches: { value: touches }
  })
  return event
}

实践下来,虽然能模拟长按事件,但无法唤起actionsheet。究其原因,应该是原生微信app顶层layer对用户的长按手势有监听。所以无论js里怎么模拟长按事件,永远不是用户的手势操作,也无法触发长按手势回调,唤起actionsheet。

微信里应该也对UIWebview加了longPress的gesture recognizer。就iOS而言,伪代码如下:

NSString *imgURL = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).src", touchPoint.x, touchPoint.y];
NSString *urlToSave = [self.webView stringByEvaluatingJavaScriptFromString:imgURL];
UILongPressGestureRecognizer* longPressed = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressed:)];
longPressed.delegate = self;
[self.webView addGestureRecognizer:longPressed];

但微信H5里长按保存图片,不止是顶层layer长按gensture的监听,也涉及到app与webview的通信。比如,app里可能会inject这段脚本到webview里进行长按事件的监听。

可以比较肯定的是,微信注入的脚本里,addEventListener的第三个参数useCapture option一定是设置成了true。因为touchstart回调里如果调用了event.preventDefault(),长按图片是无法唤起保存图片的actionsheet。

let startTime = 0;
document.addEventListner('touchstart', function() {
  startTime = Date.now();
}, true)

document.addEventListener('touchend', function() {
  const endTime = Date.now();
  if (endTime - startTime > 3000) {
    // dispatchLongTimeEvent();
  }
}, true)

终上,我无法解决如何在微信H5里点击保存图片。

如果H5仅仅是微信内置浏览器打开的网页,就需要微信提供相应的API支持;

如果H5是微信小程序打开的webview,需要webview和微信小程序可以通信,并调用微信小程序下载图片的API。

转载于:https://zhuanlan.zhihu.com/p/260941705 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值