>

动用Canvas实时管理Video

- 编辑:澳门新葡亰平台游戏 -

动用Canvas实时管理Video

HTML5:使用Canvas实时处理Video

2015/09/11 · HTML5 · Canvas

本文由 伯乐在线 - cucr 翻译,唐尤华 校稿。未经许可,禁止转载!
英文出处:mozilla。欢迎加入翻译组。

结合HTML5下的videocanvas的功能,你可以实时处理视频数据,为正在播放的视频添加各种各样的视觉效果。本教程演示如何使用JavaScript代码实现chroma-keying特效(也被称为“绿色屏幕效应”)。

请看这个实例.

HTML 5 Video, Canvas, ECMAScript 6

Range对象的方法


文档内容

本文使用的XHTML文档如下所示。

XHTML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "; <html xmlns="; <head> <style> body { background: black; color:#CCCCCC; } #c2 { background-image: url(foo.png); background-repeat: no-repeat; } div { float: left; border :1px solid #444444; padding:10px; margin: 10px; background:#3B3B3B; } </style> <script type="text/javascript;version=1.8" src="main.js"></script> </head> <body onload="processor.doLoad()"> <div> <video id="video" src="video.ogv" controls="true"/> </div> <div> <canvas id="c1" width="160" height="96"/> <canvas id="c2" width="160" height="96"/> </div> </body> </html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <style>
      body {
        background: black;
        color:#CCCCCC;
      }
      #c2 {
        background-image: url(foo.png);
        background-repeat: no-repeat;
      }
      div {
        float: left;
        border :1px solid #444444;
        padding:10px;
        margin: 10px;
        background:#3B3B3B;
      }
    </style>
    <script type="text/javascript;version=1.8" src="main.js"></script>
  </head>
 
  <body onload="processor.doLoad()">
    <div>
      <video id="video" src="video.ogv" controls="true"/>
    </div>
    <div>
      <canvas id="c1" width="160" height="96"/>
      <canvas id="c2" width="160" height="96"/>
    </div>
  </body>
</html>

以上代码关键部分如下:

1.创建了两个canvas元素,ID分别为c1和c2。c1用于显示当前帧的原始视频,c2是用来显示执行chroma-keying特效后的视频;c2预加载了一张静态图片,将用来取代视频中的背景色部分。
2.JavaScript代码从main.js文件导入;这段脚本使用JavaScript 1.8的特性,所以在导入脚本时,第22行中指定了版本。
3.当网页加载时,main.js中的processor.doLoad()方法会运行。

<html>
<head>
<title>HTML5 Video+Canvas with ECMAScript 6</title>
<script>
class Position{
    static caculate(obj){
        let x = 0, y = 0;
        if(obj.offsetLeft && obj.offsetTop){
            x = obj.offsetLeft, y = obj.offsetTop;
        }
        let p;
        if(obj.parentNode){
            p = Position.caculate(obj.parentNode);
            x = x + p.x, y = y + p.y;
        }
        return {'x': x, 'y': y};
    }
}
class Player{
    constructor(w = 640, h = 360){
        this.w = w, this.h = h;
        this.video  = document.createElement('video');
        this.canvas = document.createElement('canvas');
        //document.body.appendChild(this.player);
        document.body.appendChild(this.canvas);
        this.video.id       = 'objPlayer'
        this.video.width    = this.w;
        this.video.height   = this.h;
        this.canvas.id      = 'objCanvas'
        this.canvas.style.position  = 'absolute';
        this.canvas.width   = this.w;
        this.canvas.height  = this.h;
        let p = Position.caculate(this.video);
        this.canvas.style.left  = p.x;
        this.canvas.style.top   = p.y;
        this.canvas.style.zIndex    = 1000;
    }
    load(url){
        this.video.src = url;
    }
    play(){
        this.video.play();
    }
    capture(){
        this.canvas.getContext('2d').drawImage(this.video, 0, 0, this.w, this.h);
    }
    record(speed){
        this.timer = window.setInterval(() => {
            this.capture();
        }, 1000/speed);
    }
}

var player;
document.onreadystatechange = function(e){
    if(document.readyState == 'complete'){
        //console.log('ready');
        player = new Player(640, 360);
        player.load('/Users/Hao/Downloads/ReshmaSaujani_2016-480p.mp4');
        player.play();
        player.record(15);
    }
}
</script>
</head>
<body>
</body>
</html>
Range对象之cloneRange、cloneContents、extractContents方法
  • cloneRange():克隆并返回一个Range对象,非Node类型,无法添加到其他节点中;
  • cloneContents():克隆Range对象下的所有子节点,并返回一个DocumentFragment对象;
  • extractContents():提取所有子节点,并返回一个DocumentFragment对象;

<small>注:
① cloneContents & extractContents操作的对象是Node(整个节点)或是Contents(节点中的所有子节点)由selectNode & selectNodeContents两个方法决定;
②DocumentFragment是一个Node类型,故可以作为Node被添加到其他Node中,添加方法:node.appendChild(fragment)

<!-- cloneRange Demo -->
<body>
  <script>
  function cloneP(argument) {
      var p = document.getElementById("clone");
      var rangeObj = document.createRange();
      rangeObj.selectNodeContents(p);
      // 返回一个新的Range对象
      var cloneObj = rangeObj.cloneRange();
      // Range对象无法作为Node添加到节点中,故下面的appendChild方法会报错
      // p.appendChild(cloneObj);
      console.log(cloneObj.toString());

  }
  </script>
  <p id="clone">这是一个将被克隆的元素</p>
  <button onclick="cloneP()">克隆</button>
</body>
<!-- cloneContents Demo -->
<body>
  <script>
  function cloneAppend() {
      var rangeObj = document.createRange();
      var p = document.getElementById("p");
      // rangeObj.selectNode(p); /*选取整个p节点*/
      rangeObj.selectNodeContents(p); /*选取p节点中的所有子节点*/
      var fragMent = rangeObj.cloneContents(); /*clone选取的内容(或节点),并返回DocumentFragMent对象*/
      p.appendChild(fragMent); /*往p节点中添加一个fragment*/
  }
  </script>
  <p id="p">克隆里面的内容并追加到该p元素中</p>
  <button onclick="cloneAppend()">克隆并追加</button>
</body>
<!-- extractContents Demo -->
<body>
  <script>
  function extractAppend(argument) {
      var src = document.getElementById("srcDiv");
      var distDiv = document.getElementById("distDiv");
      var rangeObj = document.createRange();
      rangeObj.selectNodeContents(src);
      /*若上方使用的是selectNode方法,则会将src的整个节点提取(剪切);若为selectNodeContents,则只提取(剪切)src节点中的内容部分*/
      var fragment = rangeObj.extractContents();
      /*若上方使用的是selectNode方法,则会将src的整个节点提取(剪切)至distDiv中;若为selectNodeContents,则只提取(剪切)src节点中的内容部分至distDiv中*/
      distDiv.appendChild(fragment);
  }
  </script>
  <!-- 内容源 -->
  <div id="srcDiv" style="background-color: #0fc; width: 300px; height: 50px;">将被提取的内容</div>
  <!-- 内容提取后的放置目标 -->
  <div id="distDiv" style="background-color: #0cf; width: 300px; height: 50px; font-weight: bold;"></div>
  <button onclick="extractAppend()">提取并追加</button>
</body>

JavaScript代码

main.js中的JS代码包含三个方法。

Range对象之insertNode方法
  • insertNode(newNode):将newNode插入到Range(范围)起始点。
<!-- inserNode Demo -->
<body>
    <script>
    function insertB() {
        var p = document.getElementById("p");
        // 获得一个选择器
        var selection = document.getSelection();
        if (selection.rangeCount > 0) {
            // 获取选择器中的第一个range(选取范围)
            var rangeObj = selection.getRangeAt(0);
            // 在range范围起始位置插入整个p节点(<p id="p">这是一个<b>粗体</b></p>)
            rangeObj.insertNode(p);
        }
    }
    </script>
    <!-- 鼠标按下弹起后触发insertB函数 -->
    <p onmouseup="insertB()">这段文字中将被插入一个b元素,根据鼠标点击或选取范围(Range)的起始位置决定插入位置。</p>
    <p id="p">这是一个<b>粗体</b></p>
</body>

初始化chroma-key

doLoad()方法在XHTML文档初始加载时调用。这个方法的作用是为chroma-key处理代码准备所需的变量,设置一个事件侦听器,当用户开始播放视频时我们能检测到。

JavaScript

doLoad: function() { this.video = document.getElementById("video"); this.c1 = document.getElementById("c1"); this.ctx1 = this.c1.getContext("2d"); this.c2 = document.getElementById("c2"); this.ctx2 = this.c2.getContext("2d"); let self = this; this.video.addEventListener("play", function() { self.width = self.video.videoWidth / 2; self.height = self.video.videoHeight / 2; self.timerCallback(); }, false); },

1
2
3
4
5
6
7
8
9
10
11
12
13
doLoad: function() {
    this.video = document.getElementById("video");
    this.c1 = document.getElementById("c1");
    this.ctx1 = this.c1.getContext("2d");
    this.c2 = document.getElementById("c2");
    this.ctx2 = this.c2.getContext("2d");
    let self = this;
    this.video.addEventListener("play", function() {
        self.width = self.video.videoWidth / 2;
        self.height = self.video.videoHeight / 2;
        self.timerCallback();
      }, false);
  },

这段代码获取XHTML文档中video元素和两个canvas元素的引用,还获取了两个canvas的图形上下文的引用。这些将在我们实现chroma-keying特效时使用。

addEventListener()监听video元素,当用户按下视频上的播放按钮时被调用。为了应对用户回放,这段代码获取视频的宽度和高度,并且减半(我们将在执行chroma-keying效果时将视频的大小减半),然后调用timerCallback()方法来启动视频捕捉和视觉效果计算。

Range对象之compareBoundaryPoints方法
  • compareBoundaryPoints(how,sourceRange):比较sourceRange边界点与当前Range边界点的位置。
    • 返回值:如果当前范围的指定边界点位于 sourceRange 指定的边界点之前,则返回 -1。如果指定的两个边界点相同,则返回 0。如果当前范围的边界点位于 sourceRange 指定的边界点之后,则返回 1。
    • how参数:声明如何执行比较操作(即比较哪些边界点)。它的合法值是 Range 接口定义的常量。
      • Range.START_TO_START - 比较两个 Range 节点的开始点
      • Range.END_TO_END - 比较两个 Range 节点的结束点
      • Range.START_TO_END - 用 sourceRange 的开始点与当前范围的结束点比较
      • Range.END_TO_START - 用 sourceRange 的结束点与当前范围的开始点比较
<!-- compareBoundaryPoints Demo -->
<body>
    <script>
    function compare(argument) {
        var b = document.getElementById("b");
        var bRange = document.createRange();
        //这里也可以为selectNodeContents,因为浏览器表现的都是"粗体"这个文本对象,但代码不同,故可根据实际情况使用。
        bRange.selectNode(b);
        // 鼠标点击也是一种selection,即鼠标在左边点一下相当于在左边选择了,选择范围即点击位置
        var selection = document.getSelection();
        if (selection.rangeCount > 0) { /*选择的range数大于0*/
            var selRange = selection.getRangeAt(0); /*获取选取的第一个Range对象*/
            /*将bRange与selRange对象进行位置比较*/
            if (selRange.compareBoundaryPoints(Range.START_TO_END, bRange) <= 0) { /*bRange的开始位置与selRange的结束位置相比;若小于0,则表示selRange的结束位置在bRange的开始位置左边*/
                alert("选取的文字在指定节点(粗体节点)左边");
            } else if (selRange.compareBoundaryPoints(Range.END_TO_START, bRange) >= 0) { /*bRange的结束位置与selRange的开始位置相比;若大于0,表示selRange的开始位置大于bRange的结束位置,即表示selRange在bRange的右边*/
                alert("选取的文字在指定节点(粗体节点)右边");
            } else { /*这里省略了另外三种情况的判断*/
                alert("另外3中情况之一");
            }
        }
    }
    </script>
    <p>这段文字中有一个加粗的<b id="b">粗体</b>,为了比较选取范围(Range)在粗体这个节点的位置</p>
    <button onclick="compare()">比较</button>
</body>

定时器回调

定时器回调函数在视频开始播放时被调用(当“播放”事件发生时),然后负责自身周期调用,为每一帧视频实现keying特效。

JavaScript

timerCallback: function() { if (this.video.paused || this.video.ended) { return; } this.computeFrame(); let self = this; setTimeout(function () { self.timerCallback(); }, 0); },

1
2
3
4
5
6
7
8
9
10
timerCallback: function() {
    if (this.video.paused || this.video.ended) {
      return;
    }
    this.computeFrame();
    let self = this;
    setTimeout(function () {
        self.timerCallback();
      }, 0);
  },

回调函数首先检查视频是否正在播放;如果没有,回调函数不做任何事并立即返回。

然后调用computeFrame()方法,该方法对当前视频帧执行chroma-keying特效。

回调函数做的最后一件事就是调用setTimeout(),来让它自身尽快地被重新调用。在真实环境中,你可能会基于视频的帧率来设置调用频率。

Range对象之collapse、detach方法
  • collapse(toStart):将Range的边界点设置为重合(折叠);
    • true:toStart的值,表示Range的结束边界点设置为开始边界点的值;
    • false:toStart的值,表示Range的开始边界点设置为结束边界点的值;
  • detach():释放Range对象,无法针对释放后的Range对象操作,否则抛出INVALID_STATE_ERR错误;常用于释放已无作用的Range对象,提高脚本运行性能。

<small>detach():浏览器内核为Gecko的版本,从v15.0开始,该方法已失效。

<!-- collapse & detach Demo-->
<body>
    <script>
    var rangeObj = document.createRange();

    // 选取节点
    function selectObj() {
        rangeObj.selectNodeContents(document.getElementById("p")); /*选取p节点*/
    }
    // 取消选取,将collapse作为选择器使用
    function unSelectObj() {
        // 将range的结束边界点设置为开始边界点相同的值,即折叠。
        // 这里即使为false,也是取消选择range范围,因为false表示将开始边界点设置为结束边界点的值
        rangeObj.collapse(true);
    }
    // 显示选取的内容,selectObj的情况下,会显示p节点的所有子节点,即"待选取的对象";unSelectObj状态下,rangeObj无选取内容,alert结果为空。
    function showObj() {
        alert(rangeObj.toString());
    }

    // 释放Range对象,释放后无法对Range对象进行操作,否则抛出INVALID_STATE_ERR错误
    // detach方法在Gecko 15.0内核开始,操作没有任何效果。
    function detachRange() {
        rangeObj.detach();
    }

    function getRangeStartOffset(toStart) {
        var sel = document.getSelection();
        if (sel.rangeCount > 0) {
            var tmpRange = sel.getRangeAt(0);
            // 通过传入不同的toStart,观察startOffset的值;
            // toStart为true时,startOffset为range开始边界点的值;为false时,startOffset为range结束边界点的值。
            tmpRange.collapse(toStart);
            // 打印tmpRange在当前节点的起点开始位置;p节点中有三个子节点,startOffset的位置按当前节点计算,从0开始
            // startOffset的位置:0所1有2子3节4点5被6选7取8,9包10括11<b>0粗1体2</b>0这1个2节3点4
            console.log(tmpRange.startOffset);
        }
    }
    </script>
    <p id="p">所有子节点被选取,包括<b>粗体</b>这个节点</p>
    <button onclick="selectObj()">选取对象</button>
    <button onclick="unSelectObj()">取消选取</button>
    <button onclick="showObj()">显示对象</button>
    <button onclick="detachRange()">释放Range对象</button>
    <button onclick="getRangeStartOffset(false)">获取Range范围的开始点并打印在console中</button>
</body>

处理视频帧数据

computeFrame()方法,如下所示,实际上负责抓取每一帧的数据和执行chroma-keying特效。

JavaScript

computeFrame: function() { this.ctx1.drawImage(this.video, 0, 0, this.width, this.height); let frame = this.ctx1.getImageData(0, 0, this.width, this.height); let l = frame.data.length / 4; for (let i = 0; i < l; i++) { let r = frame.data[i * 4 + 0]; let g = frame.data[i * 4 + 1]; let b = frame.data[i * 4 + 2]; if (g > 100 && r > 100 && b < 43) frame.data[i * 4 + 3] = 0; } this.ctx2.putImageData(frame, 0, 0); return; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
computeFrame: function() {
    this.ctx1.drawImage(this.video, 0, 0, this.width, this.height);
    let frame = this.ctx1.getImageData(0, 0, this.width, this.height);
    let l = frame.data.length / 4;
 
    for (let i = 0; i < l; i++) {
      let r = frame.data[i * 4 + 0];
      let g = frame.data[i * 4 + 1];
      let b = frame.data[i * 4 + 2];
      if (g > 100 && r > 100 && b < 43)
        frame.data[i * 4 + 3] = 0;
    }
    this.ctx2.putImageData(frame, 0, 0);
    return;
  }

当它被调用后,video元素将显示最近的视频帧数据,如下所示:

图片 1

在第2行,视频帧被复制到第一个canvas ctx1的图形上下文中,高度和宽度值指定为我们之前保存的帧大小的一半。注意,您可以通过传递video元素到绘图上下文的drawImage()方法来绘制当前视频帧。其结果是:

图片 2

第3行代码通过调用第一个canvas上下文的getImageData()方法,来获取原始图像数据当前视频帧的一个副本。它提供了原始的32位像素图像数据,这样我们就能够进行操作。第4行代码通过将帧图像数据的总长度除以4,来计算图像的总像素数。

第6行代码循环扫描所有像素,获取每个像素的红、绿、蓝值,同时和预定义的背景色进行比较,这些背景色将用foo.png中导入的背景图像替换。

被检测成背景的每一个像素,将它的alpha值替换为零,表明该像素是完全透明的。结果,最终的图像背景部分是100%透明的,这样在第13行代码,把它被绘制到目标的上下文中时,效果是内容叠加到静态背景上。

由此产生的图像看起来像这样:

图片 3

在视频播放时反复这样做,这样一帧接一帧处理,呈现出chroma-key的特效。

请看这个实例。

1 赞 1 收藏 评论

HTML5新增元素audio(音频)与video(视频)


关于作者:cucr

图片 4

新浪微博:@hop_ping 个人主页 · 我的文章 · 17

图片 5

audio元素
  • audio的属性:
    • autoplay:如果出现该属性,则音频在就绪后马上播放;
    • controls:如果出现该属性,则向用户显示控件,比如播放按钮;
    • src:要播放的音频的 URL;
    • loop:如果出现该属性,则每当音频结束时重新开始播放;
    • preload:如果出现该属性,则音频在页面加载时进行加载,并预备播放。如果使用 "autoplay",则忽略该属性。
vedio元素
  • vedio的属性:
    • autoplay:如果出现该属性,则视频在就绪后马上播放;
    • controls:如果出现该属性,则向用户显示控件,比如播放按钮;
    • src:要播放的视频的 URL;
    • loop:如果出现该属性,则每当视频结束时重新开始播放;
    • preload:如果出现该属性,则视频在页面加载时进行加载,并预备播放。如果使用 "autoplay",则忽略该属性;
    • height、width:定义视频的高度与宽度;
    • muted:规定视频的音频输出应该被静音;
    • poster:规定视频下载时显示的图像URL,或者在用户点击播放按钮前显示的图像URL。
source元素

为媒介元素(比如 <video> 和 <audio>)定义媒介资源。主要用途为允许您规定可替换的视频/音频文件供浏览器根据它对媒体类型或者编解码器的支持进行选择。该元素为媒介元素的子标签(即元素应放在媒介元素之内)。

  • 常用属性src,定义资源URL;
<!-- audio Demo-->
<body>
    <!-- 当autoplay存在时,preload(预加载)忽略 -->
    <!-- <audio src="../raw/1.mp3" controls="controls" preload="preload" autoplay="autoplay" loop="loop"></audio> -->
    <audio id="audio" src="../raw/1.mp3">您的浏览器不支持</audio>
    <button onclick="playPause()">播放/暂停</button>
    <script>
    // 若再全局变量中声明,须注意该节点是否已加载(浏览器从上往下逐步加载),否则会出现获取不到的情况。
    var audio = document.getElementById("audio");

    function playPause(argument) {
        if (audio.paused) { /*如果音乐为暂停状态*/
            // 播放音乐
            audio.play();
        } else {
            audio.pause();
        }
    }
    </script>
</body>

<!--video & source Demo-->
<body>
    <button onclick="playPause()">播放/暂停</button>
    <button onclick="bigger()">放大</button>
    <button onclick="smaller()">缩小</button>
    <!-- 一种文件格式的video,弊端:某些浏览器可能出现不支持对应的视频格式 -->
    <video src="../raw/movie.ogg" id="myControls">您的浏览器不支持</video>
    <script>
    var video = document.getElementById("myControls");

    // 播放暂停功能
    function playPause(argument) {
        if (video.paused) {
            video.play();
        } else {
            video.pause();
        }
    }
    // 放大视频窗口功能
    function bigger(argument) {
        video.width = 800; /*将video节点的宽度设置为800px*/
        video.height = 800; /*将video节点的高度设置为800px*/
    }
    // 缩小视频窗口功能
    function smaller(argument) {
        video.width = 100;
        video.height = 100;
    }
    </script>
    <!-- 根据浏览器支持的格式获取对应的资源文件 -->
    <video controls="controls">
        <!-- ogg格式 -->
        <source src="../raw/movie.ogg">
        <!-- mp4格式 -->
        <source src="../raw/movie.mp4">
    </video>
</body>

HTML5的拖放功能


<small>先了解这些知识(原理部分)
1.开始拖动某元素,会触发该元素的ondragstart事件;在这个事件里设置拖动的元素。
2.拖动的元素A经过元素B时,会触发B元素的ondragover事件;在这个事件里设置B元素允许被其他元素放入。
3.拖动的元素A在元素B上放手后,会出发B元素的ondrop事件;在这个事件里设置A元素放入到B元素的添加行为。
4.拖放中的数据传输,依赖于event中的dataTransfer对象。

网页元素之间的拖放
  • 拖放流程
    1. 开始拖动,在被拖动的元素上调用ondragstart监听事件;
    2. 设置拖动数据,在ondragstart事件中,通过事件的dataTransfer设置拖放数据(setData方法);
    3. 设置拖放位置,在拖放目标位置,设置元素允许被其他元素放入,通过ondragover事件避免浏览器默认方式(不允许元素被其他元素放入)处理;
    4. 获取拖动的元素并将该元素放入目的位置元素,通过ondrop事件,完成拖放元素添加到目标元素的动作。
  • 实现方法(两种)
    • 通过JS实现
      获取拖动元素及目标位置元素,通过拖动元素A监听ondragstart事件,设置拖动数据;通过目标位置元素B监听ondragover和ondrop事件,设置允许拖入,并将A添加到B元素。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTML5网页中元素的拖放-通过JS实现</title>
    <style>
    div {
        float: left;
        height: 300px;
        width: 300px;
    }
    #box {
        background-color: #ECAD12;
    }
    #secBox {
        background-color: #34CA7E;
    }
    </style>
    <script>
    // 页面加载完成之后立即执行如下函数
    window.onload = function() {
            var box = document.getElementById("box");
            var secBox = document.getElementById("secBox");
            var dragImg = document.getElementById("dragImg");
            // 第一步:在被拖动的节点上使用ondragstart方法,表示开始拖动;同时设置拖动的数据
            dragImg.ondragstart = function(ev) {
                // 设置拖动的数据,"myData"表示一个types元素(可以理解为id),将被拖动数据放入目标位置时获取数据使用
                // "myData"会写入到dataTransfer对象的types键(该键下包含很多类型,myData表示一种类型)中
                // 数据的传输是通过event的dataTransfer来实现的
                // ev.target.id:事件目标的id,这里指的是dragImg对象的元素id属性值
                ev.dataTransfer.setData("myData", ev.target.id);
            }
            // 第二步:ondragover规定在何处放置被拖动数据,默认情况下,无法将元素/数据放到其他数据中
            // 此处自定义ondragover事件,阻止浏览器使用默认处理方式,即允许其他元素放到该元素中
            box.ondragover = unDefault;
            secBox.ondragover = unDefault;
            // 第三步:放置数据时会出发ondrop事件,故通过重写该事件的处理方式达到放置目的。
            box.ondrop = imgDrag;
            secBox.ondrop = imgDrag;
            // ondragover调用的方法:避免浏览器对数据的默认处理方式
            function unDefault(event) {
                // 避免浏览器对数据的默认处理方式的方法
                event.preventDefault();
            }
            // ondrop调用的方法:获取拖动数据,放入到目的地节点
            function imgDrag(ev) {
                showObj(ev.dataTransfer, "dataTransfer");
                showObj(ev.dataTransfer.types, "types"); //types中会显示setData设置的数据
                showObj(ev.dataTransfer.files, "files"); //files的length若大于0,表示有有文件拖放行为;这里是img元素,所以该属性的length=0;
                // 网页元素拖放实际测试发现,drop事件不改变默认处理方式,也能拖放至目的地;前提是dragover事件中需避免默认处理。ondrop的默认处理方式是以链接形式打开。
                // 本地资源拖放则必须使用该方法避免默认处理方式执行
                ev.preventDefault();
                // 获取设置的数据标记(id)
                var myData = ev.dataTransfer.getData("myData");
                // 通过数据标记获得一个节点
                var myImg = document.getElementById(myData);
                //将节点加入追加到拖放目的地节点中 ev.target表示事件目标,即拖放目的地节点
                ev.target.appendChild(myImg);
            }
        }
        // 显示对象key和value信息
    function showObj(obj, flag) {
        for (var key in obj) { /*obj为键值对*/
            str += key + ":" + obj[key] + " -- " + flag + "<br/>";
        }
        var objDiv = document.getElementById("objDiv");
        objDiv.innerHTML = str + "<br/>";
    }
    </script>
</head>
<body>
    <!-- 被拖放数据放置的区域1 -->
    <div id="box"></div>
    <!-- 被拖放数据放置的区域2 -->
    <div id="secBox"></div>
    <img id="dragImg" src="../raw/submit.gif">
    <p id="objDiv"></p>
</body>
</html>

* *通过属性实现*

在拖动元素中设置ondragstart属性,设置事件调用的方法drag(event);在目标位置元素中设置ondragover与ondrop属性,并以此设置allowDrag(event)与allDrop(event)方法。

<small>方法名可自定义,参数必须是event</small>

  ```html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTML5网页中元素的拖放-通过属性实现</title>
<style>
div {
width: 500px;
height: 300px;
float: left;
}
#box {
background-color: #EABD14;
}
#secBox {
background-color: #CE30C6;
}
</style>
<script>
var str = "";
// 第一步:被拖放数据设置数据,开始拖放
function drag(ev) {
// "test"会写入到dataTransfer对象的types键中
ev.dataTransfer.setData("test", ev.target.id);
}
// 第二步:拖放目的地避免默认处理方式(不允许其他元素放到自身元素中,避免后允许),决定被拖放数据的位置
function dragOver(ev) {
ev.preventDefault();
}
// 第三步:放置数据触发的事件,将设置的数据获取,并将得到的数据获取节点后追加到目的地节点
function boxDrop(ev) {
showObj(ev.dataTransfer, "dataTransfer");
showObj(ev.dataTransfer.types, "types"); //types中会显示setData设置的数据
showObj(ev.dataTransfer.files, "files"); //files的length若大于0,表示有有文件拖放行为
// showObj(ev.dataTransfer.files);
// 实际测试发现,drop事件不改变默认处理方式,也能拖放至目的地;前提是dragover事件中需避免默认处理
// 本地资源拖放则必须使用该方法避免默认处理方式执行
ev.preventDefault();
var img = document.getElementById(ev.dataTransfer.getData("test"));
ev.target.appendChild(img);
}
// 显示对象key和value信息
function showObj(obj, flag) {
for (var key in obj) { /obj为键值对/
str += key + ":" + obj[key] + " -- " + flag + "
";
}
var objDiv = document.getElementById("objDiv");
objDiv.innerHTML = str + "
";
}
</script>
</head>
<body>

<div ondragover="dragOver(event)" ondrop="boxDrop(event)" id="box"></div>
<div ondragover="dragOver(event)" ondrop="boxDrop(event)" id="secBox"></div>

<img draggable="true" id="gif" ondragstart="drag(event)" src="../raw/submit.gif">
<div id="objDiv"></div>
</body>
</html>

#####本地资源拖放至网页(以图片为例)
  * **拖放流程**
    1. 设置拖放位置,在拖放目标位置,设置元素允许被其他元素放入,通过ondragover事件避免浏览器默认方式(不允许元素被其他元素放入)处理;
    2. 获取拖动的元素并将该元素放入目的位置元素,通过ondrop事件,完成拖放元素添加到目标元素的动作。

  > <small>注:
1.因为是本地资源的拖放,所以没有元素需要ondragstart事件;
2.获取拖动数据的方法:var file = event.dataTransfer.files[0];files是一个数组,访问拖动的第一个文件,所以索引值为0;
3.创建一个FileReader容器读取文件:var fileReader = new FileReader();
4.设置文件读取进度的监听事件:fileReader.onload = allDrop(readerEvent);读取完成后执行allDrop函数;
5.在allDrop文件中使用readerEvent.target.result属性获取读取结果数据并将输入添加到目标容器(拖放目的地)中;target为当前event的filereader对象。
6.开始读取文件:fileReader.readAsDataURL(file),将文件读取为一串Data URL字符串。`读取文件的方法请根据文件类型决定`。

  * **实现方法(两种)**
    * *通过JS实现*
通过目标位置元素监听ondragover和ondrop事件,设置允许拖入,读取拖动的本地资源数据并将其添加到B元素中。
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTML5之本地资源拖放到网页-通过JS实现</title>
    <style>
    #imgContainer {
        background-color: #ccc;
        height: 300px;
        width: 300px;
    }
    </style>
    <script>
    // 监听页面加载完成后执行如下函数
    window.onload = function() {
        // 获取装载图片的容器
        var imgContainer = document.getElementById("imgContainer");
        // 设置图片加载的位置,取消默认浏览器行为;即允许元素放入其他元素
        imgContainer.ondragover = function(ev) {
            ev.preventDefault();
        }
        // 放置图片
        imgContainer.ondrop = function(ev) {
            // 本地资源拖放,必须禁止浏览器默认行为(默认行为:以链接形式打开)
            ev.preventDefault();
            // 获取第一个文件
            var uploadFile = ev.dataTransfer.files[0];
            var fileReader = new FileReader();
            // 文件成功读取完成后执行如下函数
            fileReader.onload = function(subEv) {
                    // target为一个FileReader对象,图片链接就是该对象下result键对应的值
                    imgContainer.innerHTML = "<img src="" + subEv.target.result + "">"
                }
                // 读取图片为DataUrl格式,若为其他类型资源,请根据实际情况选择fileReader的读取文件方法
            fileReader.readAsDataURL(uploadFile);
        }
    }
    </script>
</head>
<body>
    <div id="imgContainer"></div>
</body>
</html>

* *通过属性实现*

在目标位置元素中设置ondragover与ondrop属性,并以此设置allowDrag(event)与allDrop(event)方法。

<small>方法名可自定义,参数必须是event</small>

  ```html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTML5之本地资源拖放到网页-通过属性实现</title>
<style>
#imgContainer {
background-color: #ccc;
height: 300px;
width: 300px;
}
</style>
</head>
<body>
<script>
var str = "";
function allowDrag(ev) {
ev.preventDefault();
}
function allDrop(ev) {
// 本地资源拖放则必须使用该方法避免默认处理方式执行
ev.preventDefault();
showObj(ev.dataTransfer, "dataTransfer");
showObj(ev.dataTransfer.types, "types");
showObj(ev.dataTransfer.files, "files");
// 可以获取到files文件列表第一个文件的文件名、最后修改时间、大小、类型等
showObj(ev.dataTransfer.files[0], "file");

    // 从File数组中获取第一个File文件
    var uploadFile = ev.dataTransfer.files[0];
    // 创建一个文件读取器
    var fileReader = new FileReader();
    // 数据读取成功完成时触发
    fileReader.onload = function(subEv) {
        showObj(subEv.target); /*target 是一个FileReader对象*/
        // subEv.target.result是一个base64加密的被拖放图片的地址
        ev.target.innerHTML = "<img src="" + subEv.target.result + "">"
    }

    // 将文件读取为一串Data URL字符串,将小文件以一种特殊格式的URL地址直接读入页面。小文件指图像与html等格式的文件。
    fileReader.readAsDataURL(uploadFile);
}
// 显示对象key和value信息
function showObj(obj, flag) {
    for (var key in obj) { /*obj为键值对*/
        str += key + ":" + obj[key] + " -- " + flag + "<br/>";
    }

    var objDiv = document.getElementById("objDiv");
    objDiv.innerHTML = str + "<br/>";
}
</script>
<div ondragover="allowDrag(event)" ondrop="allDrop(event)" id="imgContainer"></div>
<div id="objDiv"></div>

</body>
</html>

##canvas元素的基本使用
***
#####canvas元素
只是一个图形容器,必须通过JS来绘制图形;可以绘制各种路径、图形、图像、文字等。
> <small>注:所有绘制均须在画布只能,超出画布范围将无法绘制。

  * ***canvas的创建方式(两种)***
    * 通过JS创建:`document.body.innerHTML = "<canvas id="canvas">您的浏览器不支持</canvas>"`
    * 直接创建:`<canvas id="canvas">您的浏览器不支持</canvas>`
  * ***canvas的2d渲染模式下的应用***
    * *绘制矩形*
      1. 获取画布:`var canvas = document.getElementById("canvas");`
      2. 获取2d模式上下文:`var ctx = canvas.getContext("2d");`
      3. 设置填充样式:`ctx.fillStyle = "rgba(255,255,0,0.5)"`,值也可以为`#ff0、yellow`格式;
      4. 绘制矩形:`ctx.fillRect(x,y,width,height);`
    * *绘制图片*
      1. 获取画布:`var canvas = document.getElementById("canvas");`
      2. 获取2d模式上下文:`var ctx = canvas.getContext("2d");`
      3. 创建图片对象:`var img = new Image()`,设置图片路径:`img.src="xxx.jpg"`;图片对象也可从网页元素中获取;
      4. 监听图片加载完成事件:`img.onload = function(event){...}`
      5. 在onload的函数体内执行`ctx.drawImage(img,0,0)`方法,该方法有多种参数类型,可参考API

> 更多Canvas的使用可查阅Mozilla提供的API:[点击查看](https://developer.mozilla.org/en-US/docs/Web/API)

```html
<!-- canvas demo-->
<body>
    <script>
    var canvas, ctx;
    window.onload = function() {
        getCanvas();
        // fillRect();
        drawImg();
    }

    function getCanvas() {
        // 通过js创建canvas
        // document.body.innerHTML = "<canvas id="canvas"></canvas>";
        // 获取canvas
        canvas = document.getElementById("canvas");
        if (canvas == null) { /*canvas获取失败的情况下,返回false*/
            return false;
        }
        // 通过2d渲染画图,获得一个上下文
        ctx = canvas.getContext("2d");
    }

    function fillRect() {
        ctx.fillStyle = "#ff0000"; /*绘图颜色*/
        ctx.rotate(45 * Math.PI / 180); /*顺时针45度*/
        // ctx.scale(2, 0.5); /* x*2,y*0.5 坐标与长宽都会根据这个因子变化*/
        ctx.fillRect(450, 0, 50, 50); /*绘制一个矩形,参数分别是x,y,width,height*/

    }

    function drawImg() {
        var img = new Image();
        // 当img成功读取完成时,执行下方函数
        img.onload = function(ev) {
            // 若图片大于画布大小,则只会绘画画布大小部分的图片
            ctx.drawImage(img, 0, 0);
        }
        img.src = "../raw/1.jpg";

    }
    </script>
    <!-- 这是直接创建的方式,更多情况下通过js创建 -->
    <canvas id="canvas" width="500px" height="500px"></canvas>
</body>

本文由前端php发布,转载请注明来源:动用Canvas实时管理Video