浏览器 拍照功能

845 阅读3分钟

有我抬联的球迷吗😂?

前言

前些天公司有个需求,需要用电脑来注册人脸🤣,用浏览器来拍照😂。网上查了一下,果然 js 有调用摄像头的 api,自己整理了一下来满足这个需求。

如果你有类似的需求,希望能帮到你😂。

调用摄像头

一共有两个api可以实现。一个是navigator.getUserMedia。(该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性);

第二种是使用 navigator.mediaDevices.getUserMedia

  • 第一种方法navigator.getUserMedia用法详见 mdn ,代码如下:
  <video id="myVideo"></video>
  (function () {
            function userMedia() {
                return navigator.getUserMedia = navigator.getUserMedia ||
                    navigator.webkitGetUserMedia ||
                    navigator.mozGetUserMedia ||
                    navigator.msGetUserMedia || null;
            }
            if (userMedia()) {
                let constraints = {
                    video: true,
                    audio: false
                };
                const media = navigator.getUserMedia(constraints, function (stream) {
                    const myVideo = document.getElementById('myVideo'),
                          url = window.URL || window.webkitURL;
                    myVideo.src = url ? url.createObjectURL(stream) : stream;
                    myVideo.play();
                }, function (error) {
                    console.log("ERROR");
                    console.log(error);
                });
            } else {
                console.log("不支持");
            }
        })();
  • 第二种方法navigator.mediaDevices.getUserMedia用法详见mdn, 代码如下:
<video id="myVideo"></video>
(function () {
      // 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象
      if (navigator.mediaDevices === undefined) {
          navigator.mediaDevices = {};
      }
      if (navigator.mediaDevices.getUserMedia === undefined) {
          navigator.mediaDevices.getUserMedia = function (constraints) {
              // 首先,如果有getUserMedia的话,就获得它
              const getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;

              // 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
              if (!getUserMedia) {
                  return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
              }

              // 否则,为老的navigator.getUserMedia方法包裹一个Promise
              return new Promise(function (resolve, reject) {
                  getUserMedia.call(navigator, constraints, resolve, reject);
              });
          }
      }
      const constraints = {
          video: true,
          audio: false
      };
      let promise = navigator.mediaDevices.getUserMedia(constraints);
      promise.then(stream => {
          const myVideo = document.getElementById('myVideo');
          // 旧的浏览器可能没有srcObject
          if ("srcObject" in v) {
              myVideo.srcObject = stream;
          } else {
              // 防止再新的浏览器里使用它,应为它已经不再支持了
              myVideo.src = window.URL.createObjectURL(stream);
          }
          v.onloadedmetadata = function (e) {
              myVideo.play();
          };
      }).catch(err => {
          console.error(err.name + ": " + err.message);
      })
  })();

拍照

设置一个标志,看看video是否在play。然后听拍照按钮的点击事件,如果videoPlaying 为 true ,使用一个canvas 获取 video 的宽高(默认 canvas 是不显示的),然后使用 canvas 的drawImage,然后使用 canvas 的 toDataURL返回一个 data url,将这个 url,设置在一个 img 标签上即可😀。

直接上代码了

import { Modal } from 'antd';
import styled from 'styled-components';

const AvatarWrapper = styled.div`
    float: left;
    position:relative;
    width:120px;
    height:160px;
    border: 1px dashed #b2b2b2;
    margin: 20px 0px 0px 0px;
`;
const BarRow = styled.i`
    position:absolute;
    width:20px;
    height:2px;
    background:#b2b2b2;
    left:50%;
    top:50%;
    margin-left: -10px;
    margin-top: -1px;
`;
const BarCol = styled.i`
    position:absolute;
    width:2px;
    height:20px;
    background:#b2b2b2;
    left:50%;
    top:50%;
    margin-left: -1px;
    margin-top: -10px;
`;
const Avatar = styled.div`
    width:120px;
    height: 160px;
    position: absolute;
    background-size: 100% 100%;
`;
const ImageInput = styled.input`
    width:120px;
    height: 160px;
    opacity: 0;
    position: absolute;
`;

let mediaStreamTrack = null;

class Demo extends Component {
    constructor(props) {
        super(props); // this.props = props
        this.selectImage = this.selectImage.bind(this);
        this.state = {
            image: null,
            imageUrl: null,
            show: false,
        }
    };
    // 选择图片
    selectImage = (e) => {
        if (e.target.files[0]) {
            if (e.target.files[0].size > 1024 * 1024 * 2) return message.error('lessThan2M');
            this.setState({
                image: e.target.files[0]
            });
            let fileReader = new FileReader();
            fileReader.readAsDataURL(e.target.files[0]);
            fileReader.onload = () => {
                this.setState({imageUrl: fileReader.result})
            }
        }
    };
    // 调用camera,获取图片
    useCameraByBrowser = () => {
        this.setState({
            show: true
        }, () => {
            setTimeout(() => {
                let video = document.querySelector('#test'),
                    streaming = false,
                    width = 360,
                    height = 480;
                video.addEventListener('canplay', function (ev) {
                    if (!streaming) {
                        height = video.videoHeight / (video.videoWidth / width);
                        video.setAttribute('width', width);
                        video.setAttribute('height', height);
                        streaming = true;
                    }
                }, false);
                // 判断浏览器是否支持navigator.mediaDevices 属性
                if (navigator.mediaDevices === undefined) {
                    navigator.mediaDevices = {}
                }
                if (navigator.mediaDevices.getUserMedia === undefined) {
                    navigator.mediaDevices.getUserMedia = function (constraints) {
                        var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
                        if (!getUserMedia) {
                            return Promise.reject(new Error('浏览器不支持'))
                        }
                        return new Promise((resolve, reject) => {
                            getUserMedia.call(navigator, constraints, resolve, reject)
                        })
                    }
                }
                navigator.mediaDevices.getUserMedia({video: {width: 120, height: 160}}).then(stream => {
                    if ("srcObject" in video) {
                        video.srcObject = stream;
                    } else {
                        video.src = window.URL.createObjectURL(stream)
                    }
                    mediaStreamTrack = typeof stream.stop === 'function' ? stream : stream.getTracks()[0];
                    video.onloadedmetadata = function (e) {
                        video.play()
                    }
                }).catch(err => {
                    console.log(err)
                })
            }, 0)
        })
    };
    // 获取摄像头图片
    getDraw = (obj) => {
        let canvas = document.createElement('canvas');
        canvas.height = 480;
        canvas.width = 360;
        canvas.getContext('2d').drawImage(obj, 0, 0, 360, 480);
        canvas.toBlob((obj) => {
            this.setState({
                image: obj,
                imageUrl: canvas.toDataURL("image/png")
            })
        }, "image/png");
        if (mediaStreamTrack) {
            mediaStreamTrack.stop()
        }
        mediaStreamTrack = null
    };
    render() {
        return (
            <div>
                <Modal visible={ this.state.show } onCancel={ () => {
                    this.setState({show: false});
                    // 拍照窗口取消,也需要停止摄像头调用
                    if (mediaStreamTrack) {
                        mediaStreamTrack.stop()
                    }
                } } onOk={ () => {
                    this.getDraw(document.getElementById('test'));
                    this.setState({show: false})
                } }>
                    <video id="test" src=""></video>
                </Modal>
                        <AvatarWrapper>
                            <BarRow/>
                            <BarCol/>
                            <Avatar style={ {backgroundImage: `url(${ this.state.imageUrl })`} }></Avatar>
                          <ImageInput type="file" onChange={ (e) => {
                                this.selectImage(e)
                            } }></ImageInput>
                        </AvatarWrapper>
                        {
                            window.location.protocol === "https:" ?
                                <Button style={ {float: 'left', marginLeft: 10, marginTop: 20} }
                                        onClick={ this.useCameraByBrowser }><Icon type="camera"/></Button> : ''
                        }
                    </Form.Item>
            </div>
        )
    }
}

总结

使用navigator.mediaDevices.getUserMedia,调用摄像头,将画面通过srcObject或者用window.URL.createObjectURL转化用于src。这一步就可以播放画面了。当点击拍照时,用canvas的drawImage方法

 canvas.getContext('2d').drawImage(obj, 0, 0, 360, 480);
        canvas.toBlob((obj) => {
            this.setState({
                image: obj,
                imageUrl: canvas.toDataURL("image/png")
            })
        }, "image/png");

转换成img可以展示的文件即可; 注意:只有HTTPS可以调用。

后记

欢迎大家来指点错误,或者有更好的方法可以分享!