MediaStream 實現帶攝像頭捕捉的表情包製作器

lijinke666發表於2019-03-02

1. 前言

之前惡搞了一張朋友的表情包,直接在百度上找了一個線上表情包製作器,突然靈光一閃,要是支援攝像頭該多好,方便又快捷 (重點是省手機記憶體,不用拍照 ? ),二話不說,開始搬磚

MediaStream 實現帶攝像頭捕捉的表情包製作器

體驗地址

2. 預想的功能點

  • 圖片支援直接貼上 和 拖拽
  • 圖片和文字縮放,支援滑鼠滾輪
  • 支援圖片翻轉
  • 支援捕捉攝像頭畫面當素材

3. 擼頁面

使用的第三方庫

  • antd 宇宙最強 ui 庫
  • react-color 取色器
  • react-draggle 拖拽
  • dom-to-image dom 節點轉成圖片

頁面 使用 React+ Antd 方便快捷,三下五除二就搞定了

    //每一行基本就是這樣子
    const operationRow = ({ icon = "edit", label, component }) => (
      <Row className={`${prefix}-item`}>
        <Col span={labelSpan} className={`${prefix}-item-label`}>
          <Button type="dashed" icon={icon}>
            {label}
          </Button>
        </Col>
        <Col
          span={valueSpan}
          offset={offsetSpan}
          className={`${prefix}-item-input`}
        >
          {component}
        </Col>
      </Row>
    );
複製程式碼

支援圖片拖拽

dragArea.addEventListener(
      "dragleave",
      e => {
        this.stopAll(e);
        this.removeDragAreaStyle();
      },
      false
    );
    //移動
    dragArea.addEventListener(
      "dragover",
      e => {
        this.stopAll(e);
        this.addDragAreaStyle();
      },
      false
    );
    dragArea.addEventListener(
      "drop",
      e => {
        this.stopAll(e);
        this.removeDragAreaStyle();
        const files = e.dataTransfer.files;
        this.renderImage(Array.from(files)[0]);
      },
      false
    );
複製程式碼

支援 圖片 貼上,這個也很簡單 繫結貼上事件 拿到 event 裡面的 data 渲染出來就行了

  pasteHandler = e => {
    const { items, types } = e.clipboardData;
    if (!items) return;

    const item = items[0]; //只要一張圖片
    const { kind, type } = item; //kind 種類 ,type 型別
    if (kind.toLocaleLowerCase() != "file") {
      return message.error("錯誤的檔案型別!");
    }
    const file = item.getAsFile();
    this.renderImage(file);
  };
  //貼上圖片
  bindPasteListener = area => {
    area.addEventListener("paste", this.pasteHandler);
  };
複製程式碼

渲染圖片

 renderImage = file => {
    if (file && Object.is(typeof file, "object")) {
      let { type, name, size } = file;
      if (!isImage(type)) {
        return message.error("無效的圖片格式");
      }
      this.setState({ loading: true });
      const url = window.URL.createObjectURL(file);
      this.setState({
        currentImg: {
          src: url,
          size: `${~~(size / 1024)}KB`,
          type
        },
        scale: defaultScale,
        loading: false,
        loadingImgReady: true
      });
    }
  };
複製程式碼

其他就沒啥好說的了,常規的頁面佈局

4. 生成圖片

生成圖片 本質上就是 利用 canvas 的 ctx.drawImage(),

繪製文字

const canvas = document.createElement(`cavans`)
const ctx = canvas.getContext(`2d`)

canvas.width = "預覽區域的寬"
canvas.height = "預覽區域的高"
//圖片
ctx.drawImage("URL",...attr)
//文字
ctx.fillText(TEXT,...attr)
//旋轉圖片
ctx.rotate(Math.PI/180 * 好多度)
//縮放 也是 呼叫 drawImage, 改變 sy,sx 繪製的其實就實現縮放了
ctx.drawImage(URL,0,0,sx /scale,sy/scale,0,0,x,y)

//轉成圖片
canvas.toDataURL(`image/png`,如果要壓縮這裡就填第二個引數)
複製程式碼

懂原理了 其實沒必要一行一行這樣寫了 找到個 現成的 dom-to-image 的庫,肥腸的不錯,也是開源和元件化得魅力啊,利人利己

呼叫 api domToimage.toPng() 輕鬆搞定

 drawMeme = () => {
    const { width, height, loadingImgReady,isCompress } = this.state;
    if (!loadingImgReady) return message.error("請選擇圖片!");

    this.setState({ drawLoading: true });

    const imageArea = document.querySelector(".preview-content");
    const options = {
      width,
      height,
    }
    if(isCompress){
      options.quality = defaultQuality
    }
    domToImage
      .toPng(imageArea, options)
      .then(dataUrl => {
        this.setState({ drawLoading: false });
        Modal.confirm({
          title: "生成成功",
          content: <img src={dataUrl} style={{ maxWidth: "100%" }} />,
          onOk: () => {
            message.success("下載成功!");
            const filename = Date.now()
            const ext = isCompress ? `jpeg` : `png`
            var link = document.createElement("a");
            link.download = `${filename}.${ext}`;
            link.href = dataUrl;
            link.click();
          },
          okText: "立即下載",
          cancelText: "再改一改"
        });
      })
      .catch(err => {
        message.error(err);
        this.setState({ drawLoading: false });
      });
  };
複製程式碼
  1. MediaStream 實現 攝像頭捕捉

要想拿到 MediaStream, 呼叫 navigatar.mediaDevices() 即可, 如果想研究 webRTC,這些 API 也是基礎, 個人不是很感興趣,就沒研究,暫時只用到這個藉口

     navigator.mediaDevices
        .getUserMedia({
          video: true,
          audio: true
        })
        .then(stream => {
          const cameraUrl = window.URL.createObjectURL(stream);
          const hide = message.loading(`盛世美顏即將出現...`)
          //其他程式碼
          this.setState(
            {
              cameraUrl,
              cameraVisible: true
            },
            () => {
              setTimeout(()=>{
                try {
                  this.video.play();
                } catch (err) {
                  console.log(err);
                  Modal.error({
                    title: "攝像頭失敗",
                    content: err.message
                  });
                } finally{
                  hide()
                }
              },1000)
              
            }
          );
        })
        .catch((err)=>{
        
          console.log(err)
          Modal.error({
            title: "呼叫攝像頭失敗",
            content: err.toString()
          });
          this.setState({ cameraVisible: false });
        });
        })
複製程式碼

這時頁面左上角 會彈出一個提示 問你是不是允許 使用攝像頭,同意後 拿到 stream,否則進入 cath

MediaStream 實現帶攝像頭捕捉的表情包製作器

使用 URL.createObjectURL() 拿到一個臨時的 url 連結

然後將 連結 設定成 <video src={臨時 URL}/> 呼叫 video.play()

//jsx
          <video
            style={{
              display:"block",
              margin:"0 auto"
            }}
            ref={video => (this.video = video)}
            src={cameraUrl}
            width={previewContentStyle.width}
            height={previewContentStyle.height}
          />
複製程式碼

這時就會看見一個 帥氣的臉龐 出現在了螢幕上 !

這時來到了最後一步,擷取畫面,也很簡單 把 video 節點畫在 canvas 上, 然後 toDataURL() 蹬蹬,搞定

  screenShotCamera = () => {
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");
    const { width, height } = previewContentStyle;
    canvas.width = width;
    canvas.height = height;
    ctx.drawImage(this.video, 0, 0, width, height);
    const data = canvas.toDataURL("image/png");
    message.success(`擷取攝像頭畫面成功!`)
    this.setState({
      currentImg: {
        src: data
      },
      cameraVisible:false,
      scale: defaultScale,
      loading: false,
      loadingImgReady: true
    });
  };
複製程式碼

5. 下載圖片

下載圖片 基於 HTML5download 屬性很好實現

   const filename = Date.now()
    const ext = isCompress ? `jpeg` : `png`
    const link = document.createElement("a");
    link.download = `${filename}.${ext}`;
    link.href = dataUrl;
    link.click();
複製程式碼

然後預設觸發一次點選事件 搞定

6. 結語

這樣一個支援 攝像頭 的 表情包製作器就完成了, 這時真的體會到了 npm 強大生態 和 元件化的 好處,
也學習到了 各種新 api 的使用! 老鐵沒毛病

程式碼 GIHUB 地址

相關文章