作者: 1chigua

  • HTML技巧

    一些实用的HTML技巧。

    更新网站图标

    通过在Favicon文件名中添加?v=2来强制浏览器下载新图标。

    1<link rel=”icon” href=”/favicon.ico?v=2″ />

    mark标签 文字高亮

    1<p><mark>1905年6月9日</mark>,设想人长生不死。</p>
    https://codepen.io/ly023/embed/YzQzaqO?height=300&default-tab=html%2Cresult&slug-hash=YzQzaqO&user=ly023&name=cp_embed_1

    图片懒加载 loading=”lazy”

    兼容性:Lazy loading via attribute for images & iframes

    不考虑兼容性的情况下,可以通过为图片添加loading="lazy"属性来实现图片懒加载:

    1<img src=”xxx.png” loading=”lazy” alt=”lazy”/>
    https://codepen.io/ly023/embed/ExNbeMr?height=265&theme-id=dark&default-tab=html%2Cresult&user=ly023&slug-hash=ExNbeMr&pen-title=html%E6%8A%80%E5%B7%A7-%E5%9B%BE%E7%89%87%E6%87%92%E5%8A%A0%E8%BD%BD-loading%3D%26quot%3Blazy%26quot%3B&name=cp_embed_2

    Picture标签

    HTML提供了<picture>标签,允许我们添加多个图片资源,并且根据不同的分辨率需求来展示不同的图片:

    1
    2
    3
    4
    5
    <picture>
    <source media=”(min-width:768px)” srcset=”https://picsum.photos/id/1000/400/400″>
    <source media=”(min-width:495px)” srcset=”https://picsum.photos/id/1000/200/200″>
    <img src=”https://picsum.photos/id/1000/600/600″/>
    </picture>

    使用.webp格式缩小图像并提高网站性能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <picture>
    <!– load .webp image if supported –>
    <source srcset=”logo.webp” type=”image/webp”>

    <!–
    Fallback if `.webp` images or <picture> tag
    not supported by the browser.
    –>
    <img src=”logo.png” alt=”logo”>
    </picture>

    datalist 输入提示

    通过<datalist>标签实现下拉选择(注意,<datalist>标签的id属性需要和input元素的list属性一致):

    1
    2
    3
    4
    5
    6
    <input list=”staffs”>
    <datalist id=”staffs”>
    <option value=”Michael”>
    <option value=”Jack”>
    <option value=”Tom”>
    </datalist>
    https://codepen.io/ly023/embed/gOLXBbg?height=300&default-tab=html%2Cresult&slug-hash=gOLXBbg&user=ly023&name=cp_embed_3

    Base URL

    当页面有大量的锚点跳转或者静态资源加载,并且这些链接在同一域名下时,可以通过<base>标签简化链接(<base>标签必须具有hreftarget属性):

    1
    2
    3
    4
    5
    6
    7
    <head>
    <base href=”https://picsum.photos/” target=”_blank”>
    </head>
    <body>
    <a href=”id/1/200/200″> source1 </a>
    <a href=”id/2/200/200″> source2 </a>
    </body>

    页面重定向(刷新)

    “5s后页面将跳转”,这个交互可以通过<meta>标签设置http-equiv="refresh"来实现:

    1<meta http-equiv=”refresh” content=”5; URL=’https://google.com’ />

    这里content属性指定了重定向发生的秒数。使用这种类型的重定向其实并不是那么的优雅,往往会显得很突兀。因此,最好在某些特殊的情况下使用它,比如在长时间用户不活动之后再重定向到目标页面。

  • get两个js小技能——JS截取视频第一帧&图片转Base64

    背景

    由于开发之前做的VisualDrag拖拽模板优化的时候,拖拽进去的图片、视频文件等需要进行截图作为封面,目前采用的截图方法是htme2canvas,使用canvas进行的截图操作,所以就会遇到这样的问题,视频和图片图床简单的使用标签加入canvas画布里面无法正确的截图成功。最后采取的措施就是将图片转为base64画入canvas,将视频截取第一帧图片,然后画进canvas,最后进行截图,最后经过折腾,这个方法好像成功了,写这篇博客进行记录下。

    JS截取视频第一帧

    截取视频的第一帧作为视频的封面是一个很常见的视频上传的做法。但这种做法一般会在上传进服务器时,在服务器进行截图了,这个操作方法在我之前的博客上有介绍过:https://qkongtao.cn/?p=560#h2-4

    但是前端有时候为了避免浪费服务器资源,可以在前端使用js直接进行截图了。

    代码如下:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>js截取视频第一帧</title>
    </head>
    
    <body>
        <div>
            <button onclick="cutImage()">截取视频第一帧</button>
        </div>
        <div>
            <!-- 注意跨域画布污染 -->
            <video width="640" height="480" src="https://upyun.qkongtao.cn/kodbox/%E8%A7%86%E9%A2%91/20221016134256839.mp4"
                controls="controls" id="fileVideo" crossorigin="anonymous">您的浏览器不支持视频播放</video>
            <div>
                <p>视频第一帧:</p> <img
                    src="https://upyun.qkongtao.cn/kodbox/%E8%A7%86%E9%A2%91/upload.png?_upt=5b974ef11679068799"
                    id="videoImg">
            </div>
        </div>
    
    </body>
    
    <script>
        function cutImage() {
            // 获取音频标签
            let video = document.getElementById('fileVideo')
            video.currentTime = 2 // 第一帧
            video.oncanplay = () => {
                let canvas = document.createElement("canvas");
                let ctx = canvas.getContext('2d') // 绘制2d
                canvas.width = video.clientWidth // 获取视频宽度
                canvas.height = video.clientHeight // 获取视频高度
                // 利用canvas对象方法绘图
                ctx.drawImage(video, 0, 0, video.clientWidth, video.clientHeight)
                // 转换成base64形式
                let imgUrl = canvas.toDataURL("image/png");
                console.log('imgUrl :>> ', imgUrl);
                document.getElementById("videoImg").src = imgUrl
            }
        }
    </script>
    
    </html>

    复制

    效果如下:

    get两个js小技能——JS截取视频第一帧&图片转Base64-左眼会陪右眼哭の博客

    需要注意的地方是视频资源跨域导致画布污染的问题,所以需要在video标签加上 crossorigin=”anonymous” 。 如果需要截图第2帧以上则需要在video.oncanplay()方法中进行截图。

    图片转Base64

    在开发的很多场景中需要用到base64图片形式进行传输,这种一般会用在缩略图的小文件图片中,提高浏览器的流畅性。 当然在canvas画布中,当进行绘制图片时,最好还是先将图片img标签转换为base64之后进行drawImage(),避免画布被污染和跨域等问题。

    代码如下:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>图片转Base64</title>
    </head>
    
    <body>
        <div>
            <button onclick="imgToBase64()">转换成base64形式</button>
        </div>
        <img id="img" src="http://upyun.qkongtao.cn/chevereto/2022/10/28/20210917152407654.jpg" alt="" width="300">
        <p>转换成base64形式</p>
        <div id="base64" style="word-break: break-all;"></div>
    </body>
    
    <script>
        function imgToBase64() {
    
            var src = document.getElementById("img").src
            getImageUrlBase64(src).then(
                (dataUrl) => {
                    base64Url = dataUrl;
                    console.log('base64Url :>> ', base64Url);
    
                    document.getElementById("base64").innerHTML = base64Url
                }
            );
        }
    
        /**@url :图片服务器上的url
         * @img :图片url对应的图片
         * */
        function getImageUrlBase64(url) {
            return new Promise((resolve, reject) => {
                const img = new Image();
                img.crossOrigin = "anonymous"; //处理跨域
                img.src = url;
                img.onload = function () {
                    const canvas = document.createElement("canvas"); //创建一个canvas元素
                    canvas.width = img.width; //把当前url对应的图片的宽度赋予canvas
                    canvas.height = img.height; //把当前url对应的图片的高度赋予canvas
                    const ctx = canvas.getContext("2d");
                    ctx.drawImage(img, 0, 0, canvas.width, canvas.height); //在画布上一比一的画出img
                    const dataUrl = canvas.toDataURL("image/jpeg"); //调用canvas的toDataURL获取.jpg的base64数据
                    resolve(dataUrl);
                };
            });
        }
    </script>
    
    </html>

    复制

    效果如下:

  • 在JS中从视频中提取帧

    我正在尝试构建一个从 JavaScript 视频中提取帧的函数。这是我想出的代码。该函数接收一个源和一个回调。从那里,我用源创建了一个视频,我想以设定的间隔在画布中绘制视频帧。

    不幸的是,返回的帧都是透明图像。

    我尝试了一些不同的东西,但我不能让它工作。有人可以帮忙吗?谢谢。

    const extractFramesFromVideo = function(src, callback) {
      var video = document.createElement('video');
      video.src = src;
    
      video.addEventListener('loadeddata', function() {
        var canvas = document.createElement('canvas');
        var context = canvas.getContext('2d');
        canvas.setAttribute('width', video.videoWidth);
        canvas.setAttribute('height', video.videoHeight);
    
        var frames = [];
        var fps = 1; // Frames per seconds to
        var interval = 1 / fps; // Frame interval
        var maxDuration = 10; // 10 seconds max duration
        var currentTime = 0; // Start at 0
    
        while (currentTime < maxDuration) {
          video.currentTime = currentTime; 
          context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
          var base64ImageData = canvas.toDataURL();
          frames.push(base64ImageData);
    
          currentTime += interval;
    
          if (currentTime >= maxDuration) {
            console.log(frames);
            callback(frames);
          }
        }
      });
    }
    
    export default extractFramesFromVideo;

    在调整您的代码以等待seeked事件并修复一些零碎之后,它似乎工作正常:

    async function extractFramesFromVideo(videoUrl, fps=25) {
      return new Promise(async (resolve) => {
    
        // fully download it first (no buffering):
        let videoBlob = await fetch(videoUrl).then(r => r.blob());
        let videoObjectUrl = URL.createObjectURL(videoBlob);
        let video = document.createElement("video");
    
        let seekResolve;
        video.addEventListener('seeked', async function() {
          if(seekResolve) seekResolve();
        });
    
        video.addEventListener('loadeddata', async function() {
          let canvas = document.createElement('canvas');
          let context = canvas.getContext('2d');
          let [w, h] = [video.videoWidth, video.videoHeight]
          canvas.width =  w;
          canvas.height = h;
    
          let frames = [];
          let interval = 1 / fps;
          let currentTime = 0;
          let duration = video.duration;
    
          while(currentTime < duration) {
            video.currentTime = currentTime;
            await new Promise(r => seekResolve=r);
    
            context.drawImage(video, 0, 0, w, h);
            let base64ImageData = canvas.toDataURL();
            frames.push(base64ImageData);
    
            currentTime += interval;
          }
          resolve(frames);
        });
    
        // set video src *after* listening to events in case it loads so fast
        // that the events occur before we were listening.
        video.src = videoObjectUrl; 
    
      });
    }
    

    Run Code Online (Sandbox Code Playgroud)

    用法:

    let frames = await extractFramesFromVideo("https://example.com/video.webm");
    

    Run Code Online (Sandbox Code Playgroud)

  • test

    6012c-9059a
    d1838-5f91d
    01150-6b8cc
    d4aca-fd50d
    a4d7a-60414
    94926-d98dc
    23329-2b0e6
    93316-dde99
    e4652-e947d
    f98d0-37e23
    a1d8b-5bc86
    0dc05-715f8
    37da2-f56fe
    29b76-4627d
    11141-312e3
    c5e3f-f9ea9

  • async 和 await 的用法

    “async函数是使用async关键字声明的函数。 async函数是AsyncFunction构造函数的实例, 并且其中允许使用await关键字。asyncawait关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise。”

    先来提取一下这段话中的关键字即:async[AsyncFunction](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction)awaitPromise ,异步

    要想更好的理解async 和 await 的用法,我们必须从异步和 Promise 开始切入

    异步函数

    ———————————————————————————————–

    异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的Promise 返回其结果。但是如果你的代码使用了异步函数,它的语法和结构会更像是标准的同步函数。

    Promise

    ———————————————————————————————–

    还是先看 mdn 文档:

    “Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。”

    一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知的值。它让您能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。

    一个 Promise 必然处于以下几种状态之一:

    • 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
    • 已兑现(fulfilled): 意味着操作成功完成。
    • 已拒绝(rejected): 意味着操作失败。

    接下来我们可以看一个例子

    function SicBo(){
        return new Promise((resolve,reject) =>{
          console.log("开始摇 ")
          setTimeout(()=>{
            let n = parseInt(Math.random()*6 + 1,10) //1~6
            resolve(n)
          },1000)
        })
      }
    

    这个函数理论上会输出一个区间为 [1,7) 的数字,让我们尝试着调用它。

    SicBo() //只会出现:"开始摇 "
    

    这次调用成功了吗?答案当然是没有,我们需要用.then来调用它

    //SicBo().then(success,fail) //then 就是然后
    SicBo().then(
    (n)=>{console.log(' 的点数是'+n)},//成功调用成功函数,失败调用失败函数
    (y)=>{console.log(' 炸了')})
    

    .then函数中的参数只不过是一个参数名而已,叫x,y,z什么的名字都是一样的,对应着 SicBo 函数中的 n。由于在这个例子中不存在摇 失败的情况,所以我们先不管它。

    调用后1s后得到输出

    "开始摇 "
    " 的点数是5"

    async/await

    ———————————————————————————————–

    接下来我们使用 async 和 await来重写这个例子

    如果我们直接声明一个 await函数如下

    function SicBo(){
        ...
    }
    let n = await SicBo()

    这时,js就会报错,它会告诉你 await只能放在 async函数里

    那么我们就创建一个有async标记的函数,然后把await放在函数里面,就不会报错了

    async function Roll(){
        let n = await SicBo()
    }
    

    这里出现了async/await的第一个关键知识点即:

    await只能放在async函数里面,如果不放在里面,代码会直接报错,不能运行。

    小知识:JS之父重写创建的 deno支持 let n = await 函数名()这种写法(JS不支持)

    整合一下代码,这就是一个最基本的 async/await的例子,await后面接一个会return一个new Promise的函数,并执行这个函数

    function SicBo(){
        return new Promise((resolve,reject) =>{
          console.log("开始摇 ")
          setTimeout(()=>{
            let n = parseInt(Math.random()*6 + 1,10) //1~6
            resolve(n)
          },1000)
        })
      }
    
    async function Roll(){
        let n = await SicBo() //一定要写括号,不写括号就不会执行 new Promise
      console.log(' 的点数是'+n)
    }
    
    Roll() //记得要调用函数
    

    在这个代码中还需要注意的是,resolve(n)得到的结果会返回给let n = await SicBo()里面的n。在let n = await SicBo()这行代码中,等号左边和等号右边是分离的。右边是1秒之前执行的,左边赋值时1s之后执行的,等号两边不是在同一时间执行的,左边要等右边执行完再执行。所以Roll函数也是要等1s才执行的,所以Roll函数是异步的,所以function前面必须加一个async,表示它的特殊性,让引擎对它进行特殊处理,如果不标记,则报错。

    try catch 捕获错误

    现在加入Promise中会失败的情况 把摇 改成猜大小

    function SicBo(bet){
        return new Promise((resolve,reject) =>{
          console.log("开始摇 ")
          setTimeout(()=>{
            let n = parseInt(Math.random()*6 + 1,10) //1~6
            if(n>3){
              if(bet === "big"){
                resolve(n) //摇出来是大,你也猜了大
              }else{
                reject(n) //摇出来是大,你猜了小
              }
            }else{
              if(bet === "small"){
                resolve(n)
              }else{
                reject(n)
              }
            }
          },1000)
        })
      }
    
    async function Roll(){
        let n = await SicBo() //一定要写括号,不写括号就不会执行 new Promise
      console.log(' 的点数是'+n)
    }
    
    Roll()
    

    运行代码的时候我们会发现只会输出“开始摇 ”,打开控制台可以发现控制台报错。

    这个错误的结果就是 的点数,显示Uncaught,所以我们要进行一段try...catch代码。

    async function Roll(){
        try{
        let n = await SicBo('big') //报错的话左边那一半就不会执行,每次都买大
        console.log(' 的点数是'+n)
        console.log('你猜中了')
    
      }
      catch(error){
        console.log(' 的点数是'+error)
        console.log('你输光了')
      }
    }
    

    我们在一直买大的情况下,成功了就会输出“你猜中了”,买错了就会输出“你输光了”。

    这时机智的你可能会发现,怎么JS里面会有try...catch呢?我这是在写Java? 我为啥要用 await和 try...catch,而不用更美观的then呢?

    用 .then实现买“大”

    SicBo("大").then(f1,f2)

    那么为什么需要await呢?

    答:因为要更像是标准的同步函数,让异步代码写成同步的样式,同步的代码会更加的直观,执行顺序会更加明确。

    .then 麻烦的地方

    代码 SicBo("大").then(f1,f2)中只进行了一次.then,那如果是两次或者是多次呢?

    两次 Promise 的结果:

    SicBo("大").then(f1,f2).then(f3,f4)
    

    这时候我们不禁要问,f3,f4什么时候执行呢?

    SicBo成功了,肯定先执行f1,猜错了就执行f2。如果f2不抛出另外的错误,依然会执行f3,f1不抛出另外的错误,也会执行f3,相反,f1或f2如果抛出错误就都会执行f4。

    多重Promise的结果

    在多重Promise的情况下,理顺函数的执行顺序是相对麻烦的,所以还是那句话await让异步代码写成同步的样式,同步的代码会更加的直观,执行顺序会更加明确

    同时玩两个

    在玩过几局之后,你可能已经输疯了,血压急剧飙升,迫切的想马上把本金捞回来,你可能就会上头的想同时参与两场猜大小。这次的规则是两次都猜中了,你就可以获得巨额的回报,但只要两次中有一次买错了,你就血本无归了。

    这时,你就会发现await的一个局限性:await只能传入一个Promise

    那就先试试用promise.all能不能解决这个问题(先把async functionRoll()注释掉)

    Promise.all

    Promise.all([promise1, promise2]).then(success1, fail1)
    

    特点

    promise1和promise2都成功才会调用success1

    我们还是一直买”大”

    Promise.all([SicBo('big'), SicBo('big')])
      .then((n)=>{console.log(' 的点数是'+n+'你猜中了')}
      , (error)=>{console.log(' 的点数是'+error+'你又输光了')})
    

    结果如下

    通过结果我们可以很清晰的看出只有两次摇 的点数都大于3,才会输出“你猜中了”。反之只要有一次点数小于等于3,就会直接输出“你又输光了”

    小结

    Promise.all接受一个数组,数组里面的每个函数都会返回一个promise,数组里面的函数都成功了,才会调用成功函数,否则调用失败函数。

    用asycn/await实现

    现在我们把目光重新聚焦于asycn/await

    我们之前说到await 后面永远接着一个promise,那么机智的你可能马上就可以反应过来了即:Promise.all也是一个promise

    Promise.all是一个新的promise,这个promise的结果基于接受的数组里面的promise决定的,在摇 的例子中是基于两个SicBo('big')决定的,可以解决多个请求(异步)的问题

    既然已经明确了Promise.all 也是一个promise,那我们就可以把它放在asycn函数里面,放在await的后面。

    async function Roll(){
        try{
        let n/* 数组 */ = await Promise.all([SicBo('big'), SicBo('big')])
        console.log(' 的点数是'+n)
        console.log('你猜中了')
    
      }
      catch(error){
        console.log(' 的点数是'+error)
        console.log('你输光了')
      }
    }
    

    效果一样,如下

    promise.race

    Promise.all相对应得就是Promise.race。

    Promise.race([promise1, promise2]).then(success1, fail1)
    

    特点

    promise1promise2只要有一个成功就会调用success1; promise1promise2只要有一个失败就会调用fail1; 总之,谁第一个成功或失败,就认为是race的成功或失败。

    Roll()的返回值

    ———————————————————————————————–

    最后 Roll()的返回值是什么呢? .log一下,把Roll()改成下面的代码

    let result = Roll()
    console.log(result)
    

    返回的是一个promise,再次说明了,所有async都会返回一个promise

    总结

    ———————————————————————————————–

    1. async/awaitpromise的语法糖,作用是为了让Promise更加完善,让代码看上去更加同步
    2. promise.all和 promise.race是为了解决多个解决(异步的结果)
    3. async就是为了标记 function ,其它没啥用
    4. 所有async都会返回一个promise
    5. await只能放在async函数里面

    郑重声明

    ———————————————————————————————–

    例子纯属玩笑,我与赌毒不共戴天!!!