Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[enhancement] optimize video & animation fluency #220

Open
corxit opened this issue May 7, 2022 · 0 comments
Open

[enhancement] optimize video & animation fluency #220

corxit opened this issue May 7, 2022 · 0 comments

Comments

@corxit
Copy link

corxit commented May 7, 2022

Hi, thanks for this demo, saved me a lot of time!

I've made some changes based on my app. Hope these tips help!

  • canvas rendered video cause fluency problem, thus I replaced canvas displaying as original <video/>
  • ratio not work properly in some cases. I made a fullscreen wrapper to keep the ratio fixed.
  • cover animation get stuck while tick() running. I made a new animation based on transform.
<template>
  <div class="scaner" ref="scaner" @click="resume">
    <!--<div class="banner" v-if="showBanner">-->
    <!--  <i class="close_icon" @click="() => showBanner = false"></i>-->
    <!--  <p class="text">若当前浏览器无法扫码,请切换其他浏览器尝试</p>-->
    <!--</div>-->

    <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">{{ active ? '扫描二维码':'扫码暂停 点击屏幕继续扫码' }}</p>
    </div>

    <div class="video-wrapper" ref="CanvasWrapper">
      <video
          class="source"
          ref="video"
      >
        <!--v-show="showPlay"-->
        <!--controls-->
      </video>
      <canvas class="canvas-video" ref="canvas" />
      <div v-if="code != null" class="code-indicator" :style="{ top: codePosition.y+'px', left:codePosition.x+'20px', }"></div>
    </div>

    <!--<button v-show="showPlay" @click="run">开始</button>-->
  </div>
</template>

<script>

// eslint-disable-next-line no-unused-vars
import adapter from 'webrtc-adapter';
import jsQR from 'jsqr';

export default {
  name: 'Scaner',
  props: {
    // 使用后置相机
    useBackCamera: {
      type: Boolean,
      default: true
    },
    // 扫描识别后停止
    stopOnScaned: {
      type: Boolean,
      default: true
    },
    drawOnfound: {
      type: Boolean,
      default: true
    },
    // 线条颜色
    lineColor: {
      type: String,
      default: '#03C03C'
    },
    // 线条宽度
    lineWidth: {
      type: Number,
      default: 2
    },

    responsive: {
      type: Boolean,
      default: false
    }
  },
  data () {
    return {
      showPlay: false,
      showBanner: true,

      // videoWidth: null,
      // videoHeight: null,

      active: false,

      code:null,
    }
  },
  computed: {
    codePosition(){
      if (this.code == null){
        return null;
      }
      return {
        x: this.code.location.topLeftCorner.x + (this.code.location.bottomRightCorner.x - this.code.location.topLeftCorner.x)/2 - 25,
        y: this.code.location.topLeftCorner.y + (this.code.location.bottomRightCorner.y - this.code.location.topLeftCorner.y)/2 - 25,
      }
    },
  },
  watch: {
    // @Depracated!
    // active: {
    //   immediate: true,
    //   handler(active) {
    //     if (!active) {
    //       this.pause();
    //       //this.fullStop();
    //     }
    //   }
    // }
  },
  methods: {
    // 画线
    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);
      }
    },
    tick () {
      if (this.$refs.video && this.$refs.video.readyState === this.$refs.video.HAVE_ENOUGH_DATA) {
        //.这里不设置会影响分辨率
        this.$refs.canvas.width = this.$refs.video.clientWidth;
        this.$refs.canvas.height = this.$refs.video.clientHeight;

        this.canvas.drawImage(this.$refs.video, 0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
        const imageData = this.canvas.getImageData(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
        let code = false;
        try {
          code = jsQR(imageData.data, imageData.width, imageData.height);
        } catch (e) {
          console.error(e);
        }
        if (code) {
          //@Deprecated!
          //this.drawBox(code.location);

          this.found(code);
        }
      }
      this.run();
    },
    // 初始化
    setup () {
      if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
        this.previousCode = null;
        this.parity = 0;
        this.active = true;
        this.canvas = this.$refs.canvas.getContext("2d");
        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;
          }
          this.$refs.video.playsInline = true;
          const playPromise = this.$refs.video.play();
          playPromise.catch(() => (this.showPlay = true));
          playPromise.then(this.run);
        };
        navigator.mediaDevices
          .getUserMedia({
            video: {
              facingMode,
              width: 1280,
              height: 720
            }
          })
          .then(handleSuccess)
          .catch(() => {
            navigator.mediaDevices
              .getUserMedia({ video: true })
              .then(handleSuccess)
              .catch(error => {
                this.$emit("error-captured", error);
              });
          });
      }
    },
    resume(){
      if (!this.active){
        this.code = null;
        this.active = true;
        this.$refs.video.play();
        this.run();
      }
    },
    pause(){
      this.active = false;
      this.$refs.video.pause();
    },
    run () {
      if (this.active) {
        setTimeout(()=>{
          this.tick();
        },50);

        //@Deprecated!
        //requestAnimationFrame(this.tick);
      }
    },
    found (code) {
      this.code = code;
      let codeString = code.data;
      this.$emit("code-scanned", codeString);
      if (this.stopOnScaned){
        this.pause();
      }

      // @Depracated!
      // if (this.previousCode !== codeString) {
      //   this.previousCode = codeString;
      // } else if (this.previousCode === codeString) {
      //   this.parity += 1;
      // }
      // if (this.parity > 2) {
      //   this.code = code;
      //   this.parity = 0;
      //   this.$emit("code-scanned", codeString);
      //   if (this.stopOnScaned){
      //     this.pause();
      //   }
      // }
    },
    // 完全停止
    fullStop () {
      if (this.$refs.video && this.$refs.video.srcObject) {
        this.$refs.video.srcObject.getTracks().forEach(t => t.stop());
      }
    }
  },
  mounted () {
    this.setup();
  },
  beforeDestroy () {
    this.fullStop();
  }
}
</script>

<style lang="css" scoped>
.scaner {
  background: #000000;
  position: absolute;
  top: 0px;
  left: 0;
  width: 100%;
  height: 100%;
}

.scaner .video-wrapper{
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  /*width: 100%;*/
  /*min-width: 100vh;*/
  /*height: 100%;*/
  /*min-height: 100vw;*/
}
.scaner .video-wrapper video.source{
  position: relative;
  min-width: 100vw;
  min-height: 100vh;
}

.scaner .video-wrapper .canvas-video{
  position: absolute;
  top: 0px;
  left: 0px;
  width: 100%;
  height: 100%;
  opacity: 0;
}
.scaner .video-wrapper .code-indicator{
  position: absolute;
  width: 50px;
  height: 50px;
  border-radius: 100px;
  background-color: #5F68E8;
  border: 3px solid #fff;
  z-index: 9999;
}

.scaner .cover {
  width: 100%;
  height: 60vh;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-60%);
  z-index: 1111;
}
.scaner .cover .tips {
  position: absolute;
  bottom: -20%;
  width: 100%;
  text-align: center;
  font-size: 14px;
  color: #FFFFFF;
  opacity: 0.8;
}
.scaner .cover .line {
  width: 80%;
  height: 6px;
  border-radius: 100%;
  margin-left: 10%;
  background: #5F68E8;
  background: linear-gradient(to right, #0000, #5F68E8, #0165FF, #5F68E8, #0000);
  position: absolute;
  animation: scan 3s infinite ease, opacity 3s infinite ease;
  animation-fill-mode: both;
}
@keyframes scan {
  0% {transform: translate3d(0,0,0)}
  100% {transform: translate3d(0,60vh,0)}
}
@keyframes opacity {
  0% {opacity:0}
  15% {opacity:1}
  45% {opacity:1}
  100% {opacity:0}
}
</style>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant