九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
前端調(diào)取攝像頭并實現(xiàn)拍照功能

作者:韓振方

https://juejin.cn/post/7289662055183597603

前言

最近接到的一個需求十分有意思,設(shè)計整體實現(xiàn)了前端仿 微信掃一掃 的功能。整理了一下思路,做一個分享。

tips: 如果想要實現(xiàn)完整掃一掃的功能,你需要掌握一些前置知識,這次我們先講如何實現(xiàn)拍照并且保存的功能。

一. window.navigator

  1. 你想調(diào)取手機的攝像頭,首先你得先檢驗當(dāng)前設(shè)備是否有攝像設(shè)備,window 身上自帶了一個 navigator 屬性,這個對象有一個叫做 mediaDevices 的屬性是我們即將用到的。

  2. 于是我們就可以先設(shè)計一個叫做 checkCamera 的函數(shù),用來在頁面剛開始加載的時候執(zhí)行。。

  3. 我們先看一下這個對象有哪些方法,你也許會看到下面的場景,會發(fā)現(xiàn)這個屬性身上只有一個值為 null 的 ondevicechange 屬性,不要怕,真正要用的方法其實在它的原型身上。 

  4. 讓我們點開它的原型屬性,注意下面這兩個方法,這是我們本章節(jié)的主角。

  5. 我們到這一步只是需要判斷當(dāng)前設(shè)備是否有攝像頭,我們先調(diào)取 enumerateDevices 函數(shù)來查看當(dāng)前媒體設(shè)備是否存在。它的返回值是一個 promise 類型,我們直接用 async 和 await 來簡化一下代碼。 

     
     從上圖可以看出,我的電腦有兩個音頻設(shè)備和一個視頻設(shè)備,那么我們就可以放下進(jìn)行下一步了。

二. 獲取攝像頭

  1. 接下來就需要用到上面提到的第二個函數(shù),navigator.getUserMedia。這個函數(shù)接收一個對象作為參數(shù),這個對象可以預(yù)設(shè)一些值,來作為我們請求攝像頭的一些參數(shù)。

  2. 這里我們的重點是 facingMode 這個屬性,因為我們掃一掃一般都是后置攝像頭 

     當(dāng)你執(zhí)行了這個函數(shù)以后,你會看到瀏覽器有如下提示:

  3. 于是你高興的點擊了允許,卻發(fā)現(xiàn)頁面沒有任何變化。

  4. 這里你需要知道,這個函數(shù)只是返回了一個媒體流信息給你,你可以這樣簡單理解剛剛我們干了什么,首先瀏覽器向手機申請我想用一下攝像頭可以嗎?在得到了你本人的確認(rèn)以后,手機將攝像頭的數(shù)據(jù)線遞給了瀏覽器,:“諾,給你?!?/span>

  5. 瀏覽器現(xiàn)在僅僅拿到了一根數(shù)據(jù)線,然而瀏覽器不知道需要將這個攝像頭渲染到哪里,它不可能自動幫你接上這根線,你需要自己找地方接上這根數(shù)據(jù)線。

  6. 這里不賣關(guān)子,我們需要請到我們的 Video 標(biāo)簽。我沒聽錯吧?那個播放視頻的 video 標(biāo)簽?沒錯,就是原生的 video 標(biāo)簽。

  7. 這里創(chuàng)建一個 video 標(biāo)簽,然后打上 ref 來獲取這個元素。

  8. 這里的關(guān)鍵點在于將流數(shù)據(jù)賦值給 video 標(biāo)簽的 srcObject 屬性。就好像你拿到了數(shù)據(jù)線,插到了顯示器上。
    (tips: 這里需要特別注意,不是 video.src 而是 video.srcObject 請務(wù)必注意)

  9. 現(xiàn)在你應(yīng)該會看到攝像頭已經(jīng)在屏幕上展示了,這里是我用電腦前置攝像頭錄制的一段視頻做成了gif。(脈動請給我打錢,哼)

三. 截取當(dāng)前畫面

  1. 這里我隨手寫了一個按鈕當(dāng)作拍攝鍵,接下來我們將實現(xiàn)點擊這個按鈕截取當(dāng)前畫面。

  2. 這里你需要知道一個前提,雖然我們現(xiàn)在看到的視頻是連貫的,但其實在瀏覽器渲染的時候,它其實是一幀一幀渲染的。就像宮崎駿有些動漫一樣,是一張一張手寫畫。

  3. 讓我們打開 Performance 標(biāo)簽卡,記錄一下打開掘金首頁的過程,可以看到瀏覽器的整個渲染過程其實也是一幀一幀拼接到一起,才完成了整個頁面的渲染。

  4. 知道了這個前提,那么舉一反三,我們就可以明白,雖然我們現(xiàn)在已經(jīng)打開了攝像頭,看到的視頻好像是在連貫展示,但其實它也是一幀一幀拼到一起的。那現(xiàn)在我們要做的事情就非常明了,當(dāng)我按下按鈕的時候,想辦法將 video 標(biāo)簽當(dāng)前的畫面保存下來。

  5. 這里不是特別容易想到,我就直接說答案了,在這個場景,我們需要用到 canvas 的一些能力。不要害怕,我目前對 canvas 的使用也不是特別熟練,今天也不會用到特別復(fù)雜的功能。

  6. 首先創(chuàng)建一個空白的 canvas 元素,元素的寬高設(shè)置為和 video 標(biāo)簽一致。

  7. 接下來是重點: 我們需要用到 canvas 的 getContext 方法,先別著急頭暈,這里你只需要知道,它接受一個字符串 '2d' 作為參數(shù)就行了,它會把這個畫布的上下文返回給你。
    ( tips 如果這里還不清楚上下文的概念,也不用擔(dān)心,這里你就簡單理解為把這個 canvas 這個元素加工了一下,幫你在它身上添加了一些新的方法而已。) 

  8. 在這個 ctx 對象身上,我們只需要用到一個 drawImage 方法即可,不需要關(guān)心其它屬性。

  9. 感覺參數(shù)有點多?沒關(guān)系,我們再精簡一下,我們只需要考慮第二個用法,也就是5參數(shù)的寫法。(sx,sy 是做裁切用到的,本文用不到,感興趣可以自行了解。)

  10. 這里先簡單解釋一下 dx 和 dy 是什么意思。在 canvas 里也存在一個看不見的坐標(biāo)系,起點也是左上角。設(shè)想你想在一個 HTML 的 body 元素里寫一個距離左邊距離 100px 距離頂部 100px的畫面,是不是得寫 margin-left:100px margin-top:100px 這樣的代碼?沒錯,這里的 dy 和 dx 也是同樣的道理。

  11. 我們再看 dwidth,和 dheight,從這個名字你就能才出來,肯定和我們將要在畫筆里畫畫的元素的寬度和高度有關(guān),是的,你猜的沒錯,它就好像你設(shè)置一個 div 元素的高度和寬度一樣,代表著你將在畫布上畫的截圖的寬高屬性。

  12. 現(xiàn)在只剩下第一個參數(shù)還沒解釋,這里直接說答案,我們可以直接將 video 標(biāo)簽填進(jìn)去,ctx 會自動將當(dāng)前 video 標(biāo)簽的這一幀畫面填寫進(jìn)去。現(xiàn)在按鈕的代碼應(yīng)該是這個樣子。

    vue
    復(fù)制代碼
    function shoot() {
    if (!videoEl.value || !wrapper.value) return;
    const canvas = document.createElement('canvas');
    canvas.width = videoEl.value.videoWidth;
    canvas.height = videoEl.value.videoHeight;
    //拿到 canvas 上下文對象
    const ctx = canvas.getContext('2d');
    ctx?.drawImage(videoEl.value, 0, 0, canvas.width, canvas.height);
    wrapper.value.appendChild(canvas);//將 canvas 投到頁面上
    }
  13. 測試一下效果。

四. 源碼

<script lang='ts' setup>import { ref, onMounted } from 'vue';
const wrapper = ref<HTMLDivElement>();const videoEl = ref<HTMLVideoElement>();
async function checkCamera() { const navigator = window.navigator.mediaDevices; const devices = await navigator.enumerateDevices(); if (devices) { const stream = await navigator.getUserMedia({ audio: false, video: { width: 300, height: 300, // facingMode: { exact: 'environment' }, //強制后置攝像頭 facingMode: 'user', //前置攝像頭 }, }); if (!videoEl.value) return;
videoEl.value.srcObject = stream; videoEl.value.play(); }}
function shoot() { if (!videoEl.value || !wrapper.value) return; const canvas = document.createElement('canvas'); canvas.width = videoEl.value.videoWidth; canvas.height = videoEl.value.videoHeight; //拿到 canvas 上下文對象 const ctx = canvas.getContext('2d'); ctx?.drawImage(videoEl.value, 0, 0, canvas.width, canvas.height); wrapper.value.appendChild(canvas);}
onMounted(() => { checkCamera();});</script><template> <div ref='wrapper' class='w-full h-full bg-red flex flex-col items-center'> <video ref='videoEl' /> <div @click='shoot' class='w-100px leading-100px text-center bg-black text-30px' > 拍攝 </div> </div></template>

五. 總結(jié)

實現(xiàn)拍照的整體思路其實很簡單,僅僅需要了解到視頻其實也是一幀一幀畫面構(gòu)成的,而 canvas 恰好有捕捉當(dāng)前幀的能力。

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
微信小程序——模擬時鐘案例
JS閃電打字特效
移動端H5頁面開發(fā)坑點指南
HTML5教程:讓我們用JS創(chuàng)建一個繪圖APP
vue中使用帶隱藏文本信息的圖片、圖片水印
設(shè)計必備!這可能是你見過最好玩的字體(附免費下載)
更多類似文章 >>
生活服務(wù)
熱點新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服