最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue實(shí)現(xiàn)瀏覽器端掃碼功能

 更新時(shí)間:2021年10月18日 16:38:36   作者:Dragonir  
本文主要介紹,通過(guò)使用基于 vue技術(shù)棧的前端開(kāi)發(fā)技術(shù),在瀏覽器端調(diào)起攝像頭,并進(jìn)行掃碼識(shí)別功能,對(duì)識(shí)別到的二維碼進(jìn)行跳轉(zhuǎn)或其他操作處理,對(duì)vue瀏覽器掃碼功能的實(shí)現(xiàn)代碼感興趣的朋友一起看看吧

背景

不久前我做了關(guān)于獲取瀏覽器攝像頭并掃碼識(shí)別的功能,本文中梳理了涉及到知識(shí)點(diǎn)及具體代碼實(shí)現(xiàn),整理成此篇文章內(nèi)容。

本文主要介紹,通過(guò)使用基于 vue 技術(shù)棧的前端開(kāi)發(fā)技術(shù),在瀏覽器端調(diào)起攝像頭 📷,并進(jìn)行掃碼識(shí)別功能,對(duì)識(shí)別到的二維碼進(jìn)行跳轉(zhuǎn)或其他操作處理。本文內(nèi)容分為背景介紹、實(shí)現(xiàn)效果、技術(shù)簡(jiǎn)介、代碼實(shí)現(xiàn)、總結(jié)等部分組成。

實(shí)現(xiàn)效果

本實(shí)例中主要有兩個(gè)頁(yè)面首頁(yè)和掃碼頁(yè),具體實(shí)現(xiàn)效果如下圖所示。

  • 首頁(yè):點(diǎn)擊 SCAN QRCODE 按鈕,進(jìn)入到掃碼頁(yè)。
  • 掃碼頁(yè):首次進(jìn)入時(shí),或彈出 獲取攝像頭訪問(wèn)權(quán)限的系統(tǒng)提示框,點(diǎn)擊允許訪問(wèn),頁(yè)面開(kāi)始加載攝像頭數(shù)據(jù)并開(kāi)始進(jìn)行二維碼捕獲拾取,若捕獲到二維碼,開(kāi)始進(jìn)行二維碼解析,解析成功后加載識(shí)別成功彈窗。

 在線體驗(yàn):https://dragonir.github.io/h5-scan-qrcode

提示:需要在有攝像頭設(shè)備的瀏覽器中豎屏訪問(wèn)。手機(jī)橫豎屏檢測(cè)小知識(shí)可前往我的另一篇文章《五十音小游戲中的前端知識(shí)》 中進(jìn)行了解。

技術(shù)簡(jiǎn)介

WebRTC API

WebRTC (Web Real-Time Communications) 是一項(xiàng)實(shí)時(shí)通訊技術(shù),它允許網(wǎng)絡(luò)應(yīng)用或者站點(diǎn),在不借助中間媒介的情況下,建立瀏覽器之間 點(diǎn)對(duì)點(diǎn)(Peer-to-Peer) 的連接,實(shí)現(xiàn)視頻流和(或)音頻流或者其他任意數(shù)據(jù)的傳輸。WebRTC 包含的這些標(biāo)準(zhǔn)使用戶在無(wú)需安裝任何插件或者第三方的軟件的情況下,創(chuàng)建 點(diǎn)對(duì)點(diǎn)(Peer-to-Peer) 的數(shù)據(jù)分享和電話會(huì)議成為可能。

三個(gè)主要接口:

  • MediaStream:能夠通過(guò)設(shè)備的攝像頭及話筒獲得視頻、音頻的同步流。
  • RTCPeerConnection:是 WebRTC 用于構(gòu)建點(diǎn)對(duì)點(diǎn)之間穩(wěn)定、高效的流傳輸?shù)慕M件。
  • RTCDataChannel:使得瀏覽器之間建立一個(gè)高吞吐量、低延時(shí)的信道,用于傳輸任意數(shù)據(jù)。

🔗 前往 MDN 深入學(xué)習(xí):WebRTC_API

WebRTC adapter

雖然 WebRTC規(guī)范已經(jīng)相對(duì)健全穩(wěn)固了,但是并不是所有的瀏覽器都實(shí)現(xiàn)了它所有的功能,有些瀏覽器需要在一些或者所有的WebRTC API上添加前綴才能正常使用。

WebRTC 組織在 github 上提供了一個(gè)WebRTC適配器(WebRTC adapter) 來(lái)解決在不同瀏覽器上實(shí)現(xiàn) WebRTC 的兼容性問(wèn)題。這個(gè)適配器是一個(gè) JavaScript墊片,它可以讓你根據(jù) WebRTC 規(guī)范描述的那樣去寫代碼,在所有支持 WebRTC 的瀏覽器中不用去寫前綴或者其他兼容性解決方法。

🔗 前往 MDN 深入學(xué)習(xí):WebRTC adapter

核心的API navigator.mediaDevices.getUserMedia

網(wǎng)頁(yè)調(diào)用攝像頭需要調(diào)用 getUserMedia API,MediaDevices.getUserMedia()會(huì)提示用戶給予使用媒體輸入的許可,媒體輸入會(huì)產(chǎn)生一個(gè) MediaStream,里面包含了請(qǐng)求的媒體類型的軌道。此流可以包含一個(gè)視頻軌道(來(lái)自硬件或者虛擬視頻源,比如相機(jī)、視頻采集設(shè)備和屏幕共享服務(wù)等等)、一個(gè)音頻軌道(同樣來(lái)自硬件或虛擬音頻源,比如麥克風(fēng)、A/D轉(zhuǎn)換器 等等),也可能是其它軌道類型。

它返回一個(gè)Promise對(duì)象,成功后會(huì) resolve 回調(diào)一個(gè)MediaStream對(duì)象;若用戶拒絕了使用權(quán)限,或者需要的媒體源不可用,promise 會(huì) reject 回調(diào)一個(gè)PermissionDeniedError或者NotFoundError。(返回的 promise對(duì)象 可能既不會(huì) resolve 也不會(huì) reject,因?yàn)橛脩舨皇潜仨氝x擇允許或拒絕。)

通??梢允褂?code>navigator.mediaDevices來(lái)獲取MediaDevices,例如:

navigator.mediaDevices.getUserMedia(constraints)
  .then(function(stream) {
    // 使用這個(gè)stream
  })
  .catch(function(err) {
    // 處理error
  })

🔗 前往 MDN 深入學(xué)習(xí):navigator.mediaDevices.getUserMedia

二維碼解析庫(kù) JSQR

jsQR 是一個(gè)純 JavaScript 二維碼解析庫(kù),該庫(kù)讀取原始圖像或者是攝像頭,并將定位,提取和解析其中的任何 QR碼。

如果要使用 jsQR 掃描網(wǎng)絡(luò)攝像頭流,則需要 ImageData 從視頻流中提取,然后可以將其傳遞給 jsQR。

jsQR 導(dǎo)出一個(gè)方法,該方法接受 4 個(gè)參數(shù),分別是解碼的 圖像數(shù)據(jù),、 以及 可選的對(duì)象 進(jìn)一步配置掃描行為。

imageData:格式為 [r0, g0, b0, a0, r1, g1, b1, a1, ...]Uint8ClampedArray( 8位無(wú)符號(hào)整型固定數(shù)組)rgba 像素值。

const code = jsQR(imageData, width, height, options);
if (code) {
  console.log('找到二維碼!', code);
}

🔗 前往 github 深入了解:jsQR

代碼實(shí)現(xiàn)

流程

整個(gè)掃碼流程如下圖所示:頁(yè)面初始化,先檢查瀏覽器是否支持 mediaDevices 相關(guān)API,瀏覽器進(jìn)行調(diào)去攝像頭,調(diào)用失敗,就執(zhí)行失敗回調(diào);調(diào)用成功,進(jìn)行捕獲視頻流,然后進(jìn)行掃碼識(shí)別,沒(méi)有掃瞄到可識(shí)別的二維碼就繼續(xù)掃描,掃碼成功后繪制掃描成功圖案并進(jìn)行成功回調(diào)。

下文內(nèi)容對(duì)流程進(jìn)行拆分,分別實(shí)現(xiàn)對(duì)應(yīng)的功能。

掃碼組件 Scaner

頁(yè)面結(jié)構(gòu)

我們先看下頁(yè)面結(jié)構(gòu),主要由 4 部分組成:

  • 提示框。
  • 掃碼框。
  • video:展示攝像頭捕獲視頻流。
  • canvas: 繪制視頻幀,用于二維碼識(shí)別。
<template>
  <div class="scaner" ref="scaner">
    <!-- 提示框:用于在不兼容的瀏覽器中顯示提示語(yǔ) -->
    <div class="banner" v-if="showBanner">
      <i class="close_icon" @click="() => showBanner = false"></i>
      <p class="text">若當(dāng)前瀏覽器無(wú)法掃碼,請(qǐng)切換其他瀏覽器嘗試</p>
    </div>
    <!-- 掃碼框:顯示掃碼動(dòng)畫 -->
    <div class="cover">
      <p class="line"></p>
      <span class="square top left"></span>
      <span class="square top right"></span>
      <span class="square bottom right"></span>
      <span class="square bottom left"></span>
      <p class="tips">將二維碼放入框內(nèi),即可自動(dòng)掃描</p>
    </div>
    <!-- 視頻流顯示 -->
    <video
      v-show="showPlay"
      class="source"
      ref="video"
      :width="videoWH.width"
      :height="videoWH.height"
      controls
    ></video>
    <canvas v-show="!showPlay" ref="canvas" />
    <button v-show="showPlay" @click="run">開(kāi)始</button>
  </div>
</template>

方法:繪制

  • 畫線。
  • 畫框(用于掃碼成功后繪制矩形圖形)。

// 畫線
drawLine (begin, end) {
  this.canvas.beginPath();
  this.canvas.moveTo(begin.x, begin.y);
  this.canvas.lineTo(end.x, end.y);
  this.canvas.lineWidth = this.lineWidth;
  this.canvas.strokeStyle = this.lineColor;
  this.canvas.stroke();
},
// 畫框
drawBox (location) {
  if (this.drawOnfound) {
    this.drawLine(location.topLeftCorner, location.topRightCorner);
    this.drawLine(location.topRightCorner, location.bottomRightCorner);
    this.drawLine(location.bottomRightCorner, location.bottomLeftCorner);
    this.drawLine(location.bottomLeftCorner, location.topLeftCorner);
  }
},

方法:初始化

  • 檢查是否支持。
  • 調(diào)起攝像頭。
  • 成功失敗處理。

// 初始化
setup () {
  // 判斷了瀏覽器是否支持掛載在MediaDevices.getUserMedia()的方法
  if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
    this.previousCode = null;
    this.parity = 0;
    this.active = true;
    this.canvas = this.$refs.canvas.getContext("2d");
    // 獲取攝像頭模式,默認(rèn)設(shè)置是后置攝像頭
    const facingMode = this.useBackCamera ? { exact: 'environment' } : 'user';
    // 攝像頭視頻處理
    const handleSuccess = stream => {
       if (this.$refs.video.srcObject !== undefined) {
        this.$refs.video.srcObject = stream;
      } else if (window.videoEl.mozSrcObject !== undefined) {
        this.$refs.video.mozSrcObject = stream;
      } else if (window.URL.createObjectURL) {
        this.$refs.video.src = window.URL.createObjectURL(stream);
      } else if (window.webkitURL) {
        this.$refs.video.src = window.webkitURL.createObjectURL(stream);
      } else {
        this.$refs.video.src = stream;
      }
      // 不希望用戶來(lái)拖動(dòng)進(jìn)度條的話,可以直接使用playsinline屬性,webkit-playsinline屬性
      this.$refs.video.playsInline = true;
      const playPromise = this.$refs.video.play();
      playPromise.catch(() => (this.showPlay = true));
      // 視頻開(kāi)始播放時(shí)進(jìn)行周期性掃碼識(shí)別
      playPromise.then(this.run);
    };
    // 捕獲視頻流
    navigator.mediaDevices
      .getUserMedia({ video: { facingMode } })
      .then(handleSuccess)
      .catch(() => {
        navigator.mediaDevices
          .getUserMedia({ video: true })
          .then(handleSuccess)
          .catch(error => {
            this.$emit("error-captured", error);
          });
      });
  }
},

方法:周期性掃描

run () {
  if (this.active) {
    // 瀏覽器在下次重繪前循環(huán)調(diào)用掃碼方法
    requestAnimationFrame(this.tick);
  }
},

方法:成功回調(diào)

// 二維碼識(shí)別成功事件處理
found (code) {
  if (this.previousCode !== code) {
    this.previousCode = code;
  } else if (this.previousCode === code) {
    this.parity += 1;
  }
  if (this.parity > 2) {
    this.active = this.stopOnScanned ? false : true;
    this.parity = 0;
    this.$emit("code-scanned", code);
  }
},

方法:停止

// 完全停止
fullStop () {
  if (this.$refs.video && this.$refs.video.srcObject) {
    // 停止視頻流序列軌道
    this.$refs.video.srcObject.getTracks().forEach(t => t.stop());
  }
}

方法:掃描

  • 繪制視頻幀。
  • 掃碼識(shí)別。

// 周期性掃碼識(shí)別
tick () {
  // 視頻處于準(zhǔn)備階段,并且已經(jīng)加載足夠的數(shù)據(jù)
  if (this.$refs.video && this.$refs.video.readyState === this.$refs.video.HAVE_ENOUGH_DATA) {
    // 開(kāi)始在畫布上繪制視頻
    this.$refs.canvas.height = this.videoWH.height;
    this.$refs.canvas.width = this.videoWH.width;
    this.canvas.drawImage(this.$refs.video, 0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
    // getImageData() 復(fù)制畫布上制定矩形的像素?cái)?shù)據(jù)
    const imageData = this.canvas.getImageData(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
    let code = false;
    try {
      // 識(shí)別二維碼
      code = jsQR(imageData.data, imageData.width, imageData.height);
    } catch (e) {
      console.error(e);
    }
    // 如果識(shí)別出二維碼,繪制矩形框
    if (code) {
      this.drawBox(code.location);
      // 識(shí)別成功事件處理
      this.found(code.data);
    }
  }
  this.run();
},

父組件

Scaner 的父組件主要加載頁(yè)面,并展示 Scaner 掃碼結(jié)果的回調(diào)。

頁(yè)面結(jié)構(gòu)

<template>
  <div class="scan">
    <!-- 頁(yè)面導(dǎo)航欄 -->
    <div class="nav">
      <a class="close" @click="() => $router.go(-1)"></a>
      <p class="title">Scan QRcode</p>
    </div>
    <div class="scroll-container">
      <!-- 掃碼子組件 -->
      <Scaner
        v-on:code-scanned="codeScanned"
        v-on:error-captured="errorCaptured"
        :stop-on-scanned="true"
        :draw-on-found="true"
        :responsive="false"
      />
    </div>
  </div>
</template>

父組件方法

import Scaner from '../components/Scaner';

export default {
  name: 'Scan',
  components: {
    Scaner
  },
  data () {
    return {
      errorMessage: "",
      scanned: ""
    }
  },
  methods: {
    codeScanned(code) {
      this.scanned = code;
      setTimeout(() => {
        alert(`掃碼解析成功: $[code]`);
      }, 200)
    },
    errorCaptured(error) {
      switch (error.name) {
        case "NotAllowedError":
          this.errorMessage = "Camera permission denied.";
          break;
        case "NotFoundError":
          this.errorMessage = "There is no connected camera.";
          break;
        case "NotSupportedError":
          this.errorMessage =
            "Seems like this page is served in non-secure context.";
          break;
        case "NotReadableError":
          this.errorMessage =
            "Couldn't access your camera. Is it already in use?";
          break;
        case "OverconstrainedError":
          this.errorMessage = "Constraints don't match any installed camera.";
          break;
        default:
          this.errorMessage = "UNKNOWN ERROR: " + error.message;
      }
      console.error(this.errorMessage);
     alert('相機(jī)調(diào)用失敗');
    }
  },
  mounted () {
    var str = navigator.userAgent.toLowerCase();
    var ver = str.match(/cpu iphone os (.*?) like mac os/);
    // 經(jīng)測(cè)試 iOS 10.3.3以下系統(tǒng)無(wú)法成功調(diào)用相機(jī)攝像頭
    if (ver && ver[1].replace(/_/g,".") < '10.3.3') {
     alert('相機(jī)調(diào)用失敗');
    }
  }

完整代碼

🔗 github: https://github.com/dragonir/h5-scan-qrcode

總結(jié)

應(yīng)用擴(kuò)展

我覺(jué)得以下幾個(gè)功能都是可以通過(guò)瀏覽器調(diào)用攝像頭并掃描識(shí)別來(lái)實(shí)現(xiàn)的,大家覺(jué)得還有哪些 很哇塞🌟 的功能應(yīng)用可以通過(guò)瀏覽器端掃碼實(shí)現(xiàn) 😂?

  • 鏈接跳轉(zhuǎn)。
  • 價(jià)格查詢。
  • 登錄認(rèn)證。
  •  文件下載。

兼容性

  • 即使使用了 adapter,getUserMedia API 在部分瀏覽器中也存在不支持的。
  • 低版本瀏覽器(如 iOS 10.3 以下)、Android 小眾瀏覽器(如 IQOO 自帶瀏覽器)不兼容。
  • QQ微信 內(nèi)置瀏覽器無(wú)法調(diào)用。

參考資料

[1]. Taking still photos with WebRTC

[2]. Choosing cameras in JavaScript with the mediaDevices API

[3]. 如何使用JavaScript訪問(wèn)設(shè)備前后攝像頭

作者:dragonir 本文地址:https://www.cnblogs.com/dragonir/p/15405141.html

到此這篇關(guān)于Vue實(shí)現(xiàn)瀏覽器端掃碼功能的文章就介紹到這了,更多相關(guān)vue瀏覽器掃碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 解決vite打包后白屏之router-view不生效問(wèn)題

    解決vite打包后白屏之router-view不生效問(wèn)題

    這篇文章主要介紹了解決vite打包后白屏之router-view不生效問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • 在vue中v-bind使用三目運(yùn)算符綁定class的實(shí)例

    在vue中v-bind使用三目運(yùn)算符綁定class的實(shí)例

    今天小編就為大家分享一篇在vue中v-bind使用三目運(yùn)算符綁定class的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-09-09
  • 簡(jiǎn)易Vue評(píng)論框架的實(shí)現(xiàn)(父組件的實(shí)現(xiàn))

    簡(jiǎn)易Vue評(píng)論框架的實(shí)現(xiàn)(父組件的實(shí)現(xiàn))

    本篇文章主要介紹了簡(jiǎn)易 Vue 評(píng)論框架的實(shí)現(xiàn)(父組件的實(shí)現(xiàn)),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-01-01
  • 在vue-cli創(chuàng)建的項(xiàng)目中使用sass操作

    在vue-cli創(chuàng)建的項(xiàng)目中使用sass操作

    這篇文章主要介紹了在vue-cli創(chuàng)建的項(xiàng)目中使用sass操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-08-08
  • vue實(shí)現(xiàn)右鍵菜單欄

    vue實(shí)現(xiàn)右鍵菜單欄

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)右鍵菜單欄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • vue實(shí)踐---vue不依賴外部資源實(shí)現(xiàn)簡(jiǎn)單多語(yǔ)操作

    vue實(shí)踐---vue不依賴外部資源實(shí)現(xiàn)簡(jiǎn)單多語(yǔ)操作

    這篇文章主要介紹了vue實(shí)踐---vue不依賴外部資源實(shí)現(xiàn)簡(jiǎn)單多語(yǔ)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-09-09
  • Vue.use()在new Vue() 之前使用的原因淺析

    Vue.use()在new Vue() 之前使用的原因淺析

    本文通過(guò)實(shí)例代碼給大家介紹了為什么Vue.use()在new Vue() 之前使用,需要的朋友可以參考下
    2019-08-08
  • vue基于websocket實(shí)現(xiàn)智能聊天及吸附動(dòng)畫效果

    vue基于websocket實(shí)現(xiàn)智能聊天及吸附動(dòng)畫效果

    這篇文章主要介紹了vue基于websocket實(shí)現(xiàn)智能聊天及吸附動(dòng)畫效果,主要功能是基于websocket實(shí)現(xiàn)聊天功能,封裝了一個(gè)socket.js文件,使用Jwchat插件實(shí)現(xiàn)類似QQ、微信電腦端的功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07
  • vue組件實(shí)例解析

    vue組件實(shí)例解析

    Tag組件其實(shí)是一個(gè)很小的組件,業(yè)務(wù)價(jià)值很低,主要用于Vue新手入門。主要實(shí)現(xiàn)Vue常用的父組件改變子組件的值,view改變model,model的變化反應(yīng)到view上,事件的綁定等功能。下面跟著小編一起來(lái)看下吧
    2017-01-01
  • 使用Vue3實(shí)現(xiàn)一個(gè)簡(jiǎn)單的思維導(dǎo)圖組件

    使用Vue3實(shí)現(xiàn)一個(gè)簡(jiǎn)單的思維導(dǎo)圖組件

    思維導(dǎo)圖是一種用于表示信息、想法和概念的圖形化工具,本文將基于 Vue3和VueDraggable實(shí)現(xiàn)一個(gè)簡(jiǎn)單的思維導(dǎo)圖組件,支持節(jié)點(diǎn)拖拽,編輯及節(jié)點(diǎn)之間的關(guān)系連接,希望對(duì)大家有所幫助
    2025-04-04

最新評(píng)論

迁安市| 河间市| 绩溪县| 阳春市| 榕江县| 白水县| 曲周县| 青铜峡市| 许昌市| 兴业县| 宜宾市| 大渡口区| 仁化县| 云南省| 昆明市| 五莲县| 永宁县| 墨脱县| 望谟县| 炎陵县| 洪洞县| 溆浦县| 秀山| 浠水县| 卢龙县| 平舆县| 阿图什市| 广饶县| 松阳县| 康马县| 柘城县| 车致| 广东省| 伊川县| 泾川县| 五大连池市| 阜南县| 九寨沟县| 伊川县| 民勤县| 中西区|