日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用html2Canvas将页面转化为canvas图片,最后长按保存到本地,史上最全 html2canvas 使用 踏坑之旅,没有之一

發(fā)布時(shí)間:2024/9/27 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用html2Canvas将页面转化为canvas图片,最后长按保存到本地,史上最全 html2canvas 使用 踏坑之旅,没有之一 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

最近工作中遇到一個(gè)需求,類似這樣

點(diǎn)擊商品二維碼,生成一張帶有商品圖片、標(biāo)題、描述、二維碼等信息的圖片,用戶長按進(jìn)行保存。

在使用html2canvas進(jìn)行項(xiàng)目開發(fā)的時(shí)候,遇到很多的問題,主要為一下方面:
1、圖片跨域問題
2、截圖不全問題
3、html2canvas在IOS13.4.1 上失效問題
4、canvas 嵌套 canvas 問題
5、img標(biāo)簽使用 base64 文件 在安卓真機(jī)上閃退問題

下面把我的探坑之旅和解決思路做個(gè)梳理 →

需求實(shí)現(xiàn)主要為以下三大步:

第一:如何生成二維碼
第二:如何生成圖片
第三:如何實(shí)現(xiàn)長按保存

  • 如何生成二維碼
    這里我使用的是 qrcode 插件(官網(wǎng)地址:https://davidshimjs.github.io/qrcodejs/)

QRCode組件 附上代碼:

import React, { PureComponent } from 'react' import QRCode from 'qrcode' import { color as d3Color } from 'd3-color'/*** 轉(zhuǎn)化css顏色值為 RGBA hex形式的值 比如: #fff => #ffffffff* @param {css color} cssColor - css顏色值*/ const convertColor = (cssColor) => {const temp = d3Color(cssColor)if (temp === null) {return undefined}const alpha = Number(((temp.a || 1) * 255).toFixed(0))const result = [temp.r, temp.g, temp.b, alpha].map((e) => {const s = e.toString('16')return s.length < 2 ? `0${s}` : s}).join('')return result }// 合并配置信息 const mergeConfig = (options) => {const {ecLevel,margin,width,color,background, // scale,} = optionsreturn {errorCorrectionLevel: ecLevel || 'M', // L, M, Q, H,margin: margin || 2,// scale: scale || 4,width: width || 100,color: {dark: convertColor(color) || '#000000ff',light: convertColor(background) || '#ffffffff',},} }export default class ReactQRCode extends PureComponent {componentDidMount = () => {this.draw()}componentDidUpdate = () => {this.draw()}draw = () => {const { value, onDrowSuccess, ...rest } = this.propsconst cfg = mergeConfig(rest)QRCode.toCanvas(this.canvas, `${value}`, cfg).then(() => {onDrowSuccess && onDrowSuccess(this.canvas.toDataURL('image/jpeg'))}).catch((err) => {window.console.error(err)})}render() {return (<canvasstyle={{ width: 0 }}ref={(ref) => {this.canvas = ref}}/>)} }

調(diào)用方式:

<QRCode value="http://abc" width={240} color="black" background="#fff" ecLevel="H" />
  • 如何生成圖片
    經(jīng)過多方考察調(diào)研,最終我使用的是 html2Canvas插件(官網(wǎng)地址:http://html2canvas.hertzen.com/)

html2Canvas的git????指數(shù)還挺高的,并且瀏覽器兼容版本還不錯(cuò)。

下面開始進(jìn)入正題→

  • 首先,想要使用html2Canvas畫圖之前,我們需要確保想要繪制的html頁面已經(jīng)生成,否則,畫出來的圖可能不完整,所以我們將畫圖的操作放到 componentDidMount 這一生命周期進(jìn)行,確保頁面已經(jīng)渲染完成。
    附上代碼:
class DrowProductQrCode extends Component {componentDidMount() {// 獲取dom節(jié)點(diǎn)this.element = document.getElementById('productQrCode')this.canvas2Image()}canvas2Image = () => {html2canvas(this.element).then((canvas) => {const url = canvas.toDataURL('image/jpeg')const oImg = document.createElement('img')oImg.href = urldocument.body.appendChild(oImg)})}render() {const { qrCodeUrl, goodImg, name, title } = this.propsreturn (<div className={styles.container} id="productQrCode"><Flex><div className={styles.goodImg}><img className={styles.img} crossOrigin="Anonymous" src={goodImg} alt="商品圖片" /></div><div className={styles.goodInfo}><div className={styles.title}>{name}</div><div className={styles.desc}>{title}</div></div></Flex><QrCode value={qrCodeUrl} width={220} /><div className={styles.tips}>掃描上面的二維碼,查看內(nèi)容</div></div>)} }

這時(shí)候我們會(huì)發(fā)現(xiàn)控制臺(tái)報(bào)錯(cuò)了

最直觀的報(bào)錯(cuò)提示: been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.
意思是我們的 圖片 跨域了,因?yàn)槲覀兊膱D片大多都存儲(chǔ)在阿里云或者其他服務(wù)器上,從我們本地去使用canvas去訪問這張圖片時(shí),會(huì)存在跨域問題。

  • 接下來,如何解決跨域問題成了關(guān)鍵
    根據(jù) html2Canvas 的官方文檔我們可以知道:

    html2Canvas為我們提供了兩個(gè)參數(shù)以解決跨域問題,而這里,根據(jù)我們的報(bào)錯(cuò)信息(by CORS policy)我們使用的就是useCORS。
    于是,我們給代碼加上這一參數(shù)
html2canvas(this.element, {useCORS: true,}).then((canvas) => {...})

結(jié)果還是不起作用,我們再一次在控制臺(tái)看見了這可怕的鮮紅字眼

這是怎么回事吶?
原來當(dāng)我們在設(shè)置 useCORS: true 這一參數(shù)時(shí),需要給img 標(biāo)簽加上 允許跨域的 標(biāo)識(shí)(crossOrigin=“Anonymous”)

像這樣

<img className={styles.img} crossOrigin="Anonymous" src={goodImg} alt="商品圖片" />

這時(shí)候我的內(nèi)心已經(jīng)小有雀躍了,持著激動(dòng)的心,顫抖的手按下了保存按鈕

啊哦。。。


這可怕的鮮紅字眼又出現(xiàn)了。。

但其中有一條信息非常值得我們關(guān)注:No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

這表明,我們需要我們的后端在我們請求這張圖片時(shí)給我么加上 Access-Control-Allow-Origin :允許跨域訪問的域名 這項(xiàng)設(shè)置,必須這張圖片是允許我們這個(gè)域 跨域訪問時(shí), 我們才能成功拿到這張圖片。
有的人很好奇,為什么平時(shí)我們的代碼中 ,使用過那么多img 標(biāo)簽,為什么沒有遇到這個(gè)問題。這是因?yàn)?我們給 img 標(biāo)簽設(shè)置了 crossOrigin=“Anonymous” ,這才導(dǎo)致的。

接下來,我就屁顛屁顛去找到我司可愛的運(yùn)維小哥,讓他把我的域給允許跨域了。

現(xiàn)在!現(xiàn)在!我感覺已經(jīng)越過了艱難險(xiǎn)阻,是時(shí)候看見光明了,我再次懷著激動(dòng)的心,顫抖的手刷新頁面

我 我 我 我去!
這鮮紅的字眼

讓我有點(diǎn)惡心了

這 這 究竟是怎么肥事,我不忙明白了。運(yùn)營小哥也仔仔細(xì)細(xì)的看了他加的配置, 寫錯(cuò)了字母

于是我的眼里又燃起了希望呀,運(yùn)營小哥一頓操作猛如虎,圖片請求還是 500

這時(shí)候,我注意到了一個(gè)問題

為什么 5f68413ce4b0c9f1400679f6.jpg 這張圖片被請求了好幾次?而且居然前面還有請求成功的。這,這。。

這時(shí)候,百度的一篇文章給了我答案

CORS的配置方法一般是針對每個(gè)訪問來源單獨(dú)配置規(guī)則,勿將多個(gè)來源駕到一個(gè)規(guī)則,多個(gè)規(guī)則之間不要有覆蓋沖突。

原來,因?yàn)槲沂窃谏唐吩斍轫撘氲?DrowProductQrCode 組件,商品詳情頁可能有很多地方在同時(shí)訪問這張商品的圖片,這就導(dǎo)致了我們的配置沖突了,這張圖片到底是走緩存還是走請求,走請求是一次還是多次?

所以我靈機(jī)一動(dòng),給我們的 卡片 DrowProductQrCode 里的這張圖片加上一個(gè)時(shí)間戳,這樣瀏覽器每次就會(huì)認(rèn)為這是一個(gè)新的請求,這樣就不在存在以上問題了。

const getTimestamp = new Date().getTime() goodImg = `${goodImg}?timestamp=${getTimestamp}`

再次懷著激動(dòng)的心,顫抖的手按下保存按鈕, 終于成功的出來了商品圖片
但是里面的二維碼卻沒有出來。。。。
這這又是為什么吶
我們在仔仔細(xì)細(xì)的康康我們代碼

我們在我們將要繪制canvas的html片段里又嵌套了一個(gè)canvas,這可如何是好,canvas畫圖的時(shí)候沒有支持這個(gè)canvas嵌套canvas的操作。

  • 接下來如何解決canvas嵌套canvas的操作問題又成了關(guān)鍵

其實(shí)這很好解決
如果不能使canvas嵌套canvas,那我們就把里面的cavas轉(zhuǎn)化成為html,不就行了,

// 在 QrCode 組件上傳入一個(gè)回調(diào)函數(shù),當(dāng)二維碼的 canvas 繪制完成之后,我們將canvas 轉(zhuǎn)化成為 base 64 的文件返回回來

<QrCode onDrowSuccess={this.drowQrCodeSuccess} value={invitaionUrl(currentUserId, id)} width={220} />


我們的再去調(diào)一下后端上傳圖片的接口,將base 64 的圖片上傳上去,得到存在我們自己服務(wù)器上的二維碼 url.

/*** 將以base64的圖片url數(shù)據(jù)轉(zhuǎn)換為Blob* @param base64 用url方式表示的base64圖片數(shù)據(jù)* @return blob 返回blob對象*/ function dataURItoBlob(dataURI) {let byteStringif (dataURI.split(',')[0].indexOf('base64') >= 0) byteString = atob(dataURI.split(',')[1])else byteString = unescape(dataURI.split(',')[1])const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]const ia = new Uint8Array(byteString.length)for (let i = 0; i < byteString.length; i++) {ia[i] = byteString.charCodeAt(i)}return new Blob([ia], { type: mimeString }) }drowQrCodeSuccess = (url) => {uploadPublicFile(dataURItoBlob(url)).then((data) => {const imgUrl = getOssFileUrl(data)this.setState({qrCodeUrl: imgUrl,})}).catch(err => console.log('err', err))}

大家一定也想問,為什么不直接用base 64 的圖片作為 img 標(biāo)簽的 url 放在 html 文件里,繼續(xù)往后面讀。。。

就這樣,我們的 二維碼 卡片 canvas終于畫出來了,普天同慶,可喜可賀 嗎?

我們突然發(fā)現(xiàn)畫出來的canvas圖不太完整,少了一些東西

頭 頭 頭有點(diǎn)大…

  • 接下來如何解決截圖不完整問題又成了關(guān)鍵

經(jīng)過多方調(diào)研發(fā)現(xiàn),是因?yàn)槲覀兊膬?nèi)容過長,出現(xiàn)了滾動(dòng)條或者其他原因?qū)е?html2Canvas 截圖不完整,網(wǎng)上有很多解決方法,但是經(jīng)過我的多方實(shí)踐,如果是出現(xiàn)了滾動(dòng)條最好用的方法還是這個(gè):

加上這兩個(gè)參數(shù)就可以了,簡單粗暴,效果完美

接下來,就是最后一步

  • 如何實(shí)現(xiàn)長按保存

二維碼卡片畫出來了,接下來就是保存圖片。
老規(guī)矩,我們先將canvas 轉(zhuǎn)化為 url

const url = canvas.toDataURL('image/jpeg')

然后寫一個(gè)長按下載函數(shù)

componentDidMount() {// 監(jiān)聽容器點(diǎn)擊事件this.longPress(this.downloadImg, this.element)}// 組件銷毀時(shí)移除監(jiān)聽事件componentWillUnmount() {this.element.removeEventListener('touchstart', this.touchstart)this.element.removeEventListener('touchend', this.touchend)}// 封裝一個(gè)長按方法longPress = () => {this.timeout = 0this.element.addEventListener('touchstart', this.touchstart, false)this.element.addEventListener('touchend', this.touchend, false)}touchstart = () => {// 長按時(shí)間超過800ms,則執(zhí)行傳入的方法this.timeout = setTimeout(this.downloadImg, 800)}touchend = () => {// 長按時(shí)間少于800ms,不會(huì)執(zhí)行傳入的方法clearTimeout(this.timeout)}// 圖片下載downloadImg = () => {const { goodQrCode, fileName } = this.propsconst oImg = document.createElement('a')oImg.download = fileNameoImg.href = goodQrCodeoImg.click()oImg.remove()}

致此,下載就此完成。在pc端操作起來特別順暢

于是,我拿出測試機(jī),在ios手機(jī)上測試, IOS手機(jī)長按會(huì)自動(dòng)調(diào)起系統(tǒng)的保存圖片方法,好像沒什么問題,雖然沒使用我們的代碼,但是目的是達(dá)到了。接下來就是安卓機(jī),

長按,閃退。。。
長按, 閃退。。。
換個(gè)安卓機(jī)
長按,閃退。。。
長按, 閃退。。。

怎么肥事。。

拿出數(shù)據(jù)線,打開uc-devtools, 連接手機(jī),真機(jī)調(diào)試一看,發(fā)現(xiàn)每次長按后,頁面就被 crash 掉了。經(jīng)過百度發(fā)現(xiàn),因?yàn)?base 64的文件太長了,在很多手機(jī)上無法支持預(yù)覽及下載。

這下明白了為什么我上面生成的 qrCode 為什么不直接使用 base 64的文件作為 img 的 src 路徑了吧。

老辦法,我們調(diào)用后端接口,將圖片上傳到我們自己的服務(wù)器,然后用后端返回的地址作為圖片鏈接。

你以為這就結(jié)束了嗎?
no no no
坑還沒踏完吶

測試在測試的時(shí)候,發(fā)現(xiàn)ios的一款手機(jī)的二維碼怎么也出不來

經(jīng)過調(diào)查發(fā)現(xiàn),我所使用的 html2canvas 版本(1.0.0-rc.7 ) 在IOS13.4.1 系統(tǒng)版本不生效,需要把它降到 html2canvas 1.0.0-rc.4 版本方可成功
附上代碼 ->

// npm 管理 // 先卸載舊版本 npm uninstall html2canvas // 安裝新版本 npm install --save html2canvas@1.0.0-rc.4// yarn 管理 // 先卸載舊版本 yarn remove html2canvas // 安裝新版本 yarn add html2canvas@1.0.0-rc.4

完美解決!

但是大家也知道,使用 a 標(biāo)簽下載圖片 基本不太現(xiàn)實(shí),他只能新開一個(gè)窗口,預(yù)覽圖片,然后用戶自己手動(dòng)截屏或者靠系統(tǒng)、瀏覽器自帶的長按保存圖片方法。想要是實(shí)現(xiàn)長按保存的效果只能靠調(diào)起 native 方法、或者后端實(shí)現(xiàn)下載功能,我們請求接口來得以實(shí)現(xiàn)。

那么問題來,如果后端和native都不愿意或者沒法實(shí)現(xiàn),產(chǎn)品又非讓你做出這個(gè)效果來
那你就… 你就… 你就… 找他理論(低頭)去

最后附上完整代碼邏輯:
GoodsDetailPage:

handleCanvas2ImageOK = (url) => {this.setState({goodQrCode: url,productQrCodeDivShow: false,})}render() {return {<div>// 商品二維碼卡片<GoodQrCodeModal// 影藏modal彈框方法hideCodeModal={this.hideCodeModal}// 是否展示modal彈框codeModalShow={codeModalShow}// qrcode 生成的二維碼上傳到后端后的url地址goodQrCode={goodQrCode}// 下載的文件名fileName={name}/>// 生成商品二維碼的HTML代碼, 通過 productQrCodeDivShow 字段控制其展示// productQrCodeDivShow 的作用就是讓GoodsDetailPage頁面渲染時(shí)將 商品二維碼卡片 生成,然后返回 商品二維碼卡片 的url, 影藏商品二維碼的HTML。{productQrCodeDivShow && (<ProductQrCodecurrentUserId={userId}detail={detail}onCanvas2ImageOK={this.handleCanvas2ImageOK}/>)}</div>} }

ProductQrCode:

/*** 將以base64的圖片url數(shù)據(jù)轉(zhuǎn)換為Blob* @param base64 用url方式表示的base64圖片數(shù)據(jù)* @return blob 返回blob對象*/ function dataURItoBlob(dataURI) {let byteStringif (dataURI.split(',')[0].indexOf('base64') >= 0) byteString = atob(dataURI.split(',')[1])else byteString = unescape(dataURI.split(',')[1])const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]const ia = new Uint8Array(byteString.length)for (let i = 0; i < byteString.length; i++) {ia[i] = byteString.charCodeAt(i)}return new Blob([ia], { type: mimeString }) }class ProductQrCode extends Component {state = {qrCodeUrl: '',}componentDidMount() {}drowQrCodeSuccess = (url) => {uploadPublicFile(dataURItoBlob(url)).then((data) => {const imgUrl = getOssFileUrl(data)this.setState({qrCodeUrl: imgUrl,})}).catch(err => console.log('err', err))}render() {const { currentUserId, detail, onCanvas2ImageOK } = this.propsconst { name, title, pics, id } = detail || []const getTimestamp = new Date().getTime()let goodImg = getObjField(getOssFileUrl(pics), '[0]')goodImg = `${goodImg}?timestamp=${getTimestamp}`const { qrCodeUrl } = this.statereturn (<div><QrCode onDrowSuccess={this.drowQrCodeSuccess} value={invitaionUrl(currentUserId, id)} width={220} />// 確保qrcode 已生成 二維碼,并且上傳到服務(wù)器獲取到url地址{qrCodeUrl && (<DrowProductQrCodeonCanvas2ImageOK={onCanvas2ImageOK}qrCodeUrl={qrCodeUrl}name={name}title={title}goodImg={goodImg}/>)}</div>)} }export default ProductQrCodeclass DrowProductQrCode extends Component {componentDidMount() {// 獲取dom節(jié)點(diǎn)this.element = document.getElementById('productQrCode')this.canvas2Image()}canvas2Image = () => {const { onCanvas2ImageOK } = this.propshtml2canvas(this.element, {// 允許跨域 (allowTaint, useCORS)設(shè)置其一useCORS: true,scrolly: 0,scrollx: 0,}).then((canvas) => {const url = canvas.toDataURL('image/jpeg')// 將canvas生成的 base64 的地址轉(zhuǎn)化為 blob(base64 過長導(dǎo)致手機(jī)下載出現(xiàn)問題) , 上傳到oss獲取圖片URLconst blobFile = dataURItoBlob(url)uploadPublicFile(blobFile).then((data) => {const imgUrl = getOssFileUrl(data)onCanvas2ImageOK && onCanvas2ImageOK(imgUrl)}).catch(err => console.log('err', err))})}render() {const { qrCodeUrl, goodImg, name, title } = this.propsreturn (<div className={styles.container} id="productQrCode"><Flex><div className={styles.goodImg}><img className={styles.img} crossOrigin="Anonymous" src={goodImg} alt="商品圖片" /></div><div className={styles.goodInfo}><div className={styles.title}>{name}</div><div className={styles.desc}>{title}</div></div></Flex><img className={styles.qrCode} crossOrigin="Anonymous" src={qrCodeUrl} alt="商品圖片" /><div className={styles.tips}>掃描上面的二維碼,查看內(nèi)容</div></div>)} }

GoodQrCodeModal:

import React from 'react' import { Modal } from 'antd-mobile' import styles from './GoodQrCodeModal.scss'class GoodQrCodeModal extends React.PureComponent {componentDidMount() {}render() {const {codeModalShow, hideCodeModal, goodQrCode, fileName,} = this.propsreturn (<ModalclassName={styles.codeModal}visible={codeModalShow}maskClosabletransparentonClose={hideCodeModal}><GoodQrCodeImg goodQrCode={goodQrCode} fileName={fileName} /></Modal>)} }export default GoodQrCodeModalclass GoodQrCodeImg extends React.PureComponent {componentDidMount() {this.element = document.getElementById('goodQrCode')// 監(jiān)聽容器點(diǎn)擊事件this.longPress(this.downloadImg, this.element)}componentWillUnmount() {this.element.removeEventListener('touchstart', this.touchstart)this.element.removeEventListener('touchend', this.touchend)}// 封裝一個(gè)長按方法longPress = () => {this.timeout = 0this.element.addEventListener('touchstart', this.touchstart, false)this.element.addEventListener('touchend', this.touchend, false)}touchstart = () => {// 長按時(shí)間超過800ms,則執(zhí)行傳入的方法this.timeout = setTimeout(this.downloadImg, 800)}touchend = () => {// 長按時(shí)間少于800ms,不會(huì)執(zhí)行傳入的方法clearTimeout(this.timeout)}// 圖片下載downloadImg = () => {const { goodQrCode, fileName } = this.propsconst oImg = document.createElement('a')oImg.download = fileNameoImg.href = goodQrCodeoImg.click()oImg.remove()}render() {const { goodQrCode } = this.propsreturn (<img id="goodQrCode" className={styles.goodQrCode} src={goodQrCode} alt="商品二維碼" />)} }

以上就是全部大致思路啦
如有bug, 請多指教??????

如果對你有幫助,就給我點(diǎn)個(gè)贊吧

總結(jié)

以上是生活随笔為你收集整理的使用html2Canvas将页面转化为canvas图片,最后长按保存到本地,史上最全 html2canvas 使用 踏坑之旅,没有之一的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。