JS如何实现一个可以当镜子照的Button

作者:有用网 阅读量:253 发布时间:2023-11-04
关键字 JavaScript

本篇内容介绍了“JS如何实现一个可以当镜子照的Button”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

    mediaDevices 的介绍

    在 MDN 中可以看到 mediaDevices 的介绍:

    JS如何实现一个可以当镜子照的Button

    可以用来获取摄像头、麦克风、屏幕等。

    它有这些 api:

    JS如何实现一个可以当镜子照的Button

    getDisplayMedia 可以用来录制屏幕,截图。

    getUserMedia 可以获取摄像头、麦克风的输入。

    JS如何实现一个可以当镜子照的Button

    我们这里用到getUserMedia 的 api

    它要指定音频和视频的参数,开启、关闭、分辨率、前后摄像头啥的:

    JS如何实现一个可以当镜子照的Button

    这里我们把 video 开启,把 audio 关闭。

    也就是这样:

    navigator.mediaDevices.getUserMedia({
        video: true,
        audio: false,
    })
    .then((stream) => {
        //...
    }).catch(e => {
        console.log(e)
    })

    把获取到的 stream 用一个 video 来展示

    navigator.mediaDevices.getUserMedia({
        video: true,
        audio: false,
    })
    .then((stream) => {
      const video = document.getElementById('video');
      video.srcObject = stream;
      video.onloadedmetadata = () => {
        video.play();
      };
    })
    .catch((e) => console.log(e));

    通过 css 的 filter 来加点感觉:

    比如加点 blur:

    video {
      filter: blur(10px);
    }

    加点饱和度:

    video {
      filter: saturate(5)
    }

    或者加点亮度:

    video: {
      filter: brightness(3);
    }

    filter 可以组合,调整调整达到这样的效果就可以了:

    video {
      filter: blur(2px) saturate(0.6) brightness(1.1);
    }

    然后调整下大小:

    video {
      width: 300px;
      height: 100px;
      filter: blur(2px) saturate(0.6) brightness(1.1);
    }

    你会发现视频的画面没有达到设置的宽高。

    这时候通过 object-fit 的样式来设置:

    video {
      width: 300px;
      height: 100px;
      object-fit: cover;
      filter: blur(2px) saturate(0.6) brightness(1.1);
    }

    cover 是充满容器,但画面显示的位置不大对,看不到脸。我想显示往下一点的画面怎么办呢?

    可以通过 object-position 来设置:

    video {
      width: 300px;
      height: 100px;
      object-fit: cover;
      filter: blur(2px) saturate(0.6) brightness(1.1);
      object-position: 0 -100px;
    }

    y 向下移动 100 px ,画面显示的位置就对了。

    其实现在还有一个特别隐蔽的问题,不知道大家发现没,就是方向是错的。照镜子的时候应该左右翻转才对。

    所以加一个 scaleX(-1),这样就可以绕 x 周反转了。

    video {
      width: 300px;
      height: 100px;
      object-fit: cover;
      filter: blur(2px) saturate(0.6) brightness(1.1);
      object-position: 0 -100px;
      transform: scaleX(-1);
    }

    这样就是镜面反射的感觉了。

    然后再就是 button 部分,这个我们倒是经常写:

    function Button({ children }) {
      const [buttonPressed, setButtonPressed] = useState(false);
    
      return (
        <div
          className={`button-wrap ${buttonPressed ? "pressed" : null}`}
        >
          <div
            className={`button ${buttonPressed ? "pressed" : null}`}
            onPointerDown={() => setButtonPressed(true)}
            onPointerUp={() => setButtonPressed(false)}
          >
             <video/>
          </div>
          <div className="text">{children}</div>
        </div>
      );
    }

    这里我用 jsx 写的,点击的时候修改 pressed 状态,设置不同的 class。

    样式部分

    :root {
      --transition: 0.1s;
      --border-radius: 56px;
    }
    
    .button-wrap {
      width: 300px;
      height: 100px;
      position: relative;
      transition: transform var(--transition), box-shadow var(--transition);
    }
    
    .button-wrap.pressed {
      transform: translateZ(0) scale(0.95);
    }
    
    .button {
      width: 100%;
      height: 100%;
      border: 1px solid #fff;
      overflow: hidden;
      border-radius: var(--border-radius);
      box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.25), 0px 8px 16px rgba(0, 0, 0, 0.15),
        0px 16px 32px rgba(0, 0, 0, 0.125);
      transform: translateZ(0);
      cursor: pointer;
    }
    
    .button.pressed {
      box-shadow: 0px -1px 1px rgba(0, 0, 0, 0.5), 0px 1px 1px rgba(0, 0, 0, 0.5);
    }
    
    .text {
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      pointer-events: none;
      color: rgba(0, 0, 0, 0.7);
      font-size: 48px;
      font-weight: 500;
      text-shadow:0px -1px 0px rgba(255, 255, 255, 0.5),0px 1px 0px rgba(255, 255, 255, 0.5);
    }

    这种 button 大家写的很多了,也就不用过多解释。

    要注意的是 text 和 video 都是绝对定位来做的居中。

    阴影的设置

    阴影的 4 个值是 x、y、扩散半径、颜色。

    我设置了个多重阴影:

    JS如何实现一个可以当镜子照的Button

    然后再改成不同透明度的黑就可以了:

    JS如何实现一个可以当镜子照的Button

    再就是按下时的阴影,设置了上下位置的 1px 黑色阴影:

    .button.pressed {
      box-shadow: 0px -1px 1px rgba(0, 0, 0, 0.5), 0px 1px 1px rgba(0, 0, 0, 0.5);
    }

    同时,按下时还有个 scale 的设置

    再就是文字的阴影,也是上下都设置了 1px 阴影,达到环绕的效果:

    text-shadow:0px -1px 0px rgba(255, 255, 255, 0.5),0px 1px 0px rgba(255, 255, 255, 0.5);

    最后,把这个 video 嵌进去就行了。

    完整代码

    import React, { useState, useEffect, useRef } from "react";
    import "./button.css";
    
    function Button({ children }) {
      const reflectionRef = useRef(null);
      const [buttonPressed, setButtonPressed] = useState(false);
    
      useEffect(() => {
        if (!reflectionRef.current) return;
        navigator.mediaDevices.getUserMedia({
            video: true,
            audio: false,
        })
        .then((stream) => {
            const video = reflectionRef.current;
            video.srcObject = stream;
            video.onloadedmetadata = () => {
            video.play();
            };
        })
        .catch((e) => console.log(e));
      }, [reflectionRef]);
    
      return (
        <div
          className={`button-wrap ${buttonPressed ? "pressed" : null}`}
        >
          <div
            className={`button ${buttonPressed ? "pressed" : null}`}
            onPointerDown={() => setButtonPressed(true)}
            onPointerUp={() => setButtonPressed(false)}
          >
            <video
              className="button-reflection"
              ref={reflectionRef}
            />
          </div>
          <div className="text">{children}</div>
        </div>
      );
    }
    
    export default Button;
    body {
      padding: 200px;
    }
    :root {
      --transition: 0.1s;
      --border-radius: 56px;
    }
    
    .button-wrap {
      width: 300px;
      height: 100px;
      position: relative;
      transition: transform var(--transition), box-shadow var(--transition);
    }
    
    .button-wrap.pressed {
      transform: translateZ(0) scale(0.95);
    }
    
    .button {
      width: 100%;
      height: 100%;
      border: 1px solid #fff;
      overflow: hidden;
      border-radius: var(--border-radius);
      box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.25), 0px 8px 16px rgba(0, 0, 0, 0.15),
        0px 16px 32px rgba(0, 0, 0, 0.125);
      transform: translateZ(0);
      cursor: pointer;
    }
    
    .button.pressed {
      box-shadow: 0px -1px 1px rgba(0, 0, 0, 0.5), 0px 1px 1px rgba(0, 0, 0, 0.5);
    }
    
    .text {
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      pointer-events: none;
      color: rgba(0, 0, 0, 0.7);
      font-size: 48px;
      font-weight: 500;
      text-shadow:0px -1px 0px rgba(255, 255, 255, 0.5),0px 1px 0px rgba(255, 255, 255, 0.5);
    }
    
    .text::selection {
      background-color: transparent;
    }
    
    .button .button-reflection {
      width: 100%;
      height: 100%;
      transform: scaleX(-1);
      object-fit: cover;
      opacity: 0.7;
      filter: blur(2px) saturate(0.6) brightness(1.1);
      object-position: 0 -100px;
    }

    #发表评论
    提交评论