学习记录

341 阅读5分钟

get请求携带query参数:

方式一:

axios.get('/xxxx?id=123456').then( res=>{}, error=>{} )

方式二:

axios.get('/xxxx',{
  params:{
    id:123456
  }
}).then( res=>{}, error=>{} )

\

js实现前端页面的上传并渲染预览

html中上传文件的唯一控件:<input type="file">

关键点:

在input文件上传元素中,当上传文件时,会触发元素的change事件,同时会在该元素对象的files属性上存放着待上传的元素组成的数组对象。
文件上传元素的onchange事件
FileReader构造函数实例
FileReader构造函数实例对象的 readAsDataURL()方法
FileReader构造函数实例对象的 onload事件
FileReader构造函数实例对象的 result属性
<head>
    <style>
        .box {
            box-sizing: border-box;
            width: 200px;
            height: 200px;
            padding: 10px;
            border: 1px solid #ccc;
        }

        .input-box {
            position: relative;
            box-sizing: border-box;
            width: 100%;
            height: 100%;
            border: 1px solid black;
            text-align: center;
        }

        span {
            line-height: 180px;
        }
				
      
      	//目的是让文件上传元素处于最上层且完全透明后能看见下面的内容,但是点击的时候实际上点击还是文件上传元素
        input {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            opacity: 0;
        }

        .preview {
            width: 100%;
            display: none;
        }
    </style>
</head>

<body>
    <div class=box>
        <div class='input-box'>
            <span>点击上传</span>
            <input type='file' class='file-input'>
        </div>
        <img src='#' class='preview'>
    </div>

    <script>
        let inputBox = document.querySelector('.input-box')
        let fileInput = document.querySelector('.file-input')
        let preview = document.querySelector('.preview')
        fileInput.addEventListener('change', function () {
            inputBox.style.display = 'none'
            let file = this.files[0]
            let reader = new FileReader()
            reader.readAsDataURL(file)
            reader.onload = function () {
                preview.src = this.result   //this.result就是以DataURL的形式读取到的文件是一个字符串,类似于data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...
                preview.style.display = 'block'
            }
        })
    </script>
</body>

如果需要服务器端处理,把字符串base64,后面的字符发送给服务器并用Base64解码就可以得到原始文件的二进制内容。

可视化拖拽和拖拽文件上传

传统做法:

  • 元素监听鼠标单击事件

    • 元素监听鼠标移动事件
    • 元素监听鼠标释放事件
    • 同时计算元素位置坐标是否在上传区域

新api方法:

  • Drag和Drop

工作原理:

  1. 给需要被拖拽的标签元素添加标签属性:draggable = 'true' ,并绑定ondrag事件
  2. 给接受拖拽元素的放置区域标签元素添加 ondragover 和 ondrop事件,同时必须在事件处理函数中阻止默认行为,因为浏览器对元素的拖拽事件的默认方式是禁止拖拽

开发常用技巧:

在被拖拽的元素上:

  • ondragstart
  • ondrag用于拖拽开始时,给事件或者被拖拽元素添加一些数据,可存放拖拽数据和拖拽元素的id
  • ondragend

在被拖拽元素想要放置的目标元素身上:

  • ondragenter
  • ondragover当被拖拽元素进入到拖拽元素的放置区域内时触发的事件,可以在这个事件中进行一些元素的样式设置,同时必须阻止默认事件行为。
  • ondragleave
  • ondrop当被拖拽元素放置到拖拽元素的放置区域内时触发的事件,在该事件中可以对元素进行真实的移动,比如先获取到被拖拽元素,然后使用appendChild方法对被拖拽元素进行移动

例子一:

image.png

image.png

image.png

例子二:

    <style>
        .wrap {
            background: #e5e5e5;
            width: 500px;
            height: 100px;
            margin: 0 auto;
            text-align: center;
            line-height: 100px;
            color: #fff;
        }

        #box {
            height: 500px;
            border: 2px solid skyblue;
        }

        #box img {
            width: 100px;
            height: 100px;
        }
    </style>
</head>

<body>
    <h1 class="wrap">
        将文件推入内部实现在框内显示
    </h1>
    <div id="box">
    </div>
    <script>
        //获取元素
        let oWrap = document.querySelector('.wrap')
        //
        oWrap.ondragover = function (ev) {
            return false;
            //防止默认触发
            ev.preventDefault()
            //防止事件冒泡
            ev.stopPropagation()
        }
        oWrap.ondrop = function (ev) {
            //获取了从外部拖进来的文件
            let file = ev.dataTransfer.files[0];
            //创建读取文件的对象,
            let oFile = new FileReader();
            //通过读取文件对象的readAsDataURL 方法读取指定的文件,此方法只读取路径
            // oFile.readAsDataURL(file)
            /*通过文件的不同类型选择不同的读取方式*/
            if (file.type.includes('image')) {
                oFile.readAsDataURL(file)
            } else if (file.type.includes('text')) {
                //解决中文乱码
                oFile.readAsText(file, 'gb2312')
            }
            //获取的结果在oFile.result上,读取为空,因为读取需要时间
            //文件信息读取完毕之后会触发oFile.onload
            oFile.onload = function () {
                let src = oFile.result;
                let dom = '';
                //第一种方式
                // box.innerHTML+=`<img src="${oFile.result}" width="50px" height="50px"  />`
                //第二种方式,此处也判断如果file.type包含image用以下这种方式
                if (file.type.includes('image')) {
                    let img = new Image();
                    img.src = src;
                    //box标签追加img元素
                    box.appendChild(img);
                } else if (file.type.includes('text')) {
                    //创建内容标签
                    dom = document.createElement('p')
                    dom.innerHTML = src
                    //box标签追加img元素
                    box.appendChild(dom);
                }
            }
        }
    </script>
</body>

image.png

image.png

image.png

前端文件下载

方式:

  1. 前后端配合
  2. 纯前端实现

前后端配合

方式一:

  • a链接标签的href属性直接指向服务器端的静态资源文件。

    <a href="URL"></a>
    
    export const exportFile = (url, fileName) => {
      const link = document.createElement('a')
      const body = document.querySelector('body')
    ​
      //关键步骤:
      link.href = url
      link.download = fileName    //a标签里有download属性可以自定义文件名
    ​
      // fix Firefox
      link.style.display = 'none'
      body.appendChild(link)
    ​
      link.click()   //自动模拟a标签的点击事件
      body.removeChild(link)   //点击后再移除
    }
    ​
    //无法监听错误信息
    

方式二:

  • 通过window.open()打开新页面下载文件

    window.open(`url`, '_self')
    //下载excel文件,后端提供接口,接口返回的是文件流,可以直接使用window.open()
    //当参数错误时,或其它原因导致接口请求失败,这时无法监听到接口返回的错误信息,需要保证请求必须是正确的且能正确返回数据流,不然打开页面会直接输出接口返回的错误信息,体验不好。
    

其他类似方法:form、iframe、location.href

以上方式,当在下载.mp3格式,或者视频文件时,浏览器会直接播放该文件,而达不到直接下载的功能,此时,当下载音视频文件时无法使用以上两种方式。

方式三:

  • 前端传参或者发送数据,后端根据接收的数据生成文件或根据参数查找出对应的数据在生成文件,然后后端在响应头中设置:Content-disposition:attachment(附件);filename="fliename.fileType"

    Content-disposition: 用于指定文件类型、文件名和文件编码等。

    Content-disposition(内容-部署)是MIME协议类型的扩展,MIME协议指示MIME用户代理如何显示附加的文件。

  • 浏览器接收到响应头后就会触发下载行为

优点:

  • 根据参数生成不同的文件,灵活性高
  • 能实现大数据量或大文件的下载

缺点:

  • 如果需要下载的是用户生成的内容(在线作图等)或者内容已经全部返回到客户端,会造成资源和带宽的浪费

方式四:

  • ajax请求下载,通过ajax请求返回Blob对象,或者ArrayBuffer对象

    第一步:请求数据
    //纯ajax
    const getBlob = (url) => {
      return new Promise((resolve,reject)=>{
        let xhr = new XMLHttpRequest()
        xhr.open('GET','url',true)
        xhr.responseType = 'blob'
        xhr.onload = ()=>{
          if(xhr.status ===200){
            resolve(xhr.response)
          }
        }
        xhr.send()
      })
    }
    ​
    ​
    //axios方法
    import axios from 'axios'
    const getFile = url => {
        return new Promise((resolve, reject) => {
            axios({
                method:'get',
                url,
                responseType: 'arraybuffer'   //重点在这行
            }).then(data => {
                resolve(data.data)
            }).catch(error => {
                reject(error.toString())
            })
        })
    }
    ​
    //注意点:
    //如果下载文件是文本类型的(如: .txt, .js之类的), 那么用responseType: 'text'也可以, 但是如果下载的文件是图片, 视频之类的, 就得用arraybuffer或blob
    ​
    ​
    ​
    //注意:
    //在上面ajax请求回来的后端的数据类型是Blob或者ArrayBuffer。其中ArrayBuffer不能直接操作,而是要通过类型数组对象或 DataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。   Blob格式的数据也需要转换,所以都需要转换。//第二步:将数据下载保存
    const saveAs = (blob,filename)=>{
      if (window.navigator.msSaveOrOpenBlob) {
        navigator.msSaveBlob(blob, filename)
      } else {
        const link = document.createElement('a')
        const body = document.querySelector('body')
    ​
        link.href = window.URL.createObjectURL(blob) // 创建对象url,将blob对象转为可操作的对象
        link.download = filename
    ​
        // fix Firefox
        link.style.display = 'none'
        body.appendChild(link)
    ​
        link.click()
        body.removeChild(link)
    ​
        window.URL.revokeObjectURL(link.href) // 移除调用 URL.createObjectURL() 创建的 URL 对象,减少内存消耗
      }
    }
    ​
    //为了解决IE(ie10 - 11)和Edge无法打开Blob URL链接的方法,微软自己有一套方法window.navigator.msSaveOrOpenBlob(blob, filename),打开并保存文件,以上代码做了简单的兼容,navigator.msSaveBlob(blob, filename)是直接保存。
    

    获取和下载组合为一个方法:

    const getBlob = (url)=>{
      return new Promise((resolve,reject)=>{
        const xhr = new XMLHttpRequest()
        xhr.open('GET','url',true)
        xhr.responseType = 'blob'
        xhr.onload=function(){
          if(xhr.status ==200){
            resolve(xhr.response)
          }
        }
        xhr.send()
      })
    }
    ​
    const saveFile = (blob,filename)=>{
        if(window.navigator.msSaveOpenBlob){
            navigator.msSaveBlob(blob,filename)
        }else{
            const link = document.createElement('a')
            const body = document.querySelector('body')
            link.href = window.URL.createObjectURL(blob)
            link.download = filename
            link.click()
            body,addpendchild(link)
            window.URL.revokeObjectURL(link.href)
        }
    }
    ​
    export const download = (url,filename)=>{
        getBlob(url).then(res=>{
            saveFile(res.filename)
        })
    }
    

    开发实际需求:

    服务器端下载视频,存储到本地,然后再播放,下载存储后播放不了,debug后发现是responseType未正确设置

    responseType值的类型可为如下

image.png

  • XMLHttpRequest.responseType 属性是一个枚举类型的属性,返回响应数据的类型。它允许我们手动的设置返回数据的类型。如果我们将它设置为一个空字符串,它将使用默认的"text"类型。

    当将responseType设置为一个特定的类型时,你需要确保服务器所返回的类型和你所设置的返回值类型是兼容的。那么如果两者类型不兼容,你会发现服务器返回的数据变成了null,即使服务器返回了数据。还有一个要注意的是,给一个同步请求设置responseType会抛出一个InvalidAccessError 的异常。

    DOMString

    在Ajax中,DOMString就等同于JS中的普通字符串。

    ArrayBuffer(又称类型化数组)

    ArrayBuffer对象用来表示通用的、固定长度的原始二进制数据缓冲区。ArrayBuffer 不能直接操作,而是要通过[类型数组对象]或 [DataView]对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。

    批量下载

    用到两个库jszipfile-saver, 通过ajax获取文件,然后用 jszip 压缩文件, 再用 file-saver 生成文件

    export const download = () => {
      const urls = ['url', 'url']   //需要下载的路径
      const zip = new JSZip()
      const cache = {}
      const promises = []
      urls.forEach((item) => {
        const promise = getBlob(item).then((data) => { // 下载文件, 并存成ArrayBuffer对象
          zip.file('下载文件名', data, { binary: true }) // 逐个添加文件
          cache[item.fileName] = data
        })
        promises.push(promise)
      })
    ​
      Promise.all(promises).then(() => {
        zip.generateAsync({ type: 'blob' }).then((content) => { // 生成二进制流
          FileSaver.saveAs(content, `打包下载.zip`) // 利用file-saver保存文件
        })
      })
    }
    

    完整代码:

    /**
     * 获取文件
     * @param url
     * @returns {Promise<any>}
     */
    const getBlob = (url) => {
      return new Promise((resolve) => {
        const xhr = new XMLHttpRequest()
    ​
        xhr.open('GET', url, true)
        xhr.responseType = 'blob'   //告诉服务器你期望的响应格式
        xhr.onload = () => {
          if (xhr.status === 200) {
            resolve(xhr.response)
          }
        }
    ​
        xhr.send()
      })
    }
    ​
    /**
     * 批量打包zip包下载
     * @param urlArr Array [{url: 下载文件的路径, fileName: 下载文件名称}]
     * @param filename zip文件名
     */
    export const download = (urlArr, filename = '打包下载') => {
      if (!urlArr.length > 0) return
      const zip = new JSZip()
      const cache = {}
      const promises = []
      urlArr.forEach((item) => {
        const promise = getBlob(item.url).then((data) => { // 下载文件, 并存成ArrayBuffer对象
          zip.file(item.fileName, data, { binary: true }) // 逐个添加文件
          cache[item.fileName] = data
        })
        promises.push(promise)
      })
    ​
      Promise.all(promises).then(() => {
        zip.generateAsync({ type: 'blob' }).then((content) => { // 生成二进制流
          FileSaver.saveAs(content, `${filename}.zip`) // 利用file-saver保存文件
        })
      })
    }
    

纯前端实现

纯前端实现并不是一定不需要后端,只是有时候后端的数据已经给到前端,用户下载的文件内容只需要现有的数据,这时候就可以是使用纯前端实现下载文件的功能来减小服务器资源和带宽的浪费。

应用场景:

  • 在线作图、在线表格输入

步骤:

  1. 将数据生成对应的data:URLs或者blob:URL
  2. 处理下载(或叫导出)方式
data: URLs

data: URLs是前缀为data:URL 字符串,格式为。

data:[<mediatype>][;base64], <data>

mediatype是个 MIME 类型的字符串,例如 "image/jpeg" 表示 JPEG 图像文件。如果被省略,则默认值为 text/plain;charset=US-ASCII。

数据转换data:URLs

第一种、对于文本类型,可以直接将数据拼接

const dataURL = data:text/plain;base64, + textData

第二种、通过window.btoa()方法

btoa()函数将二进制数据的“字符串”创建base-64编码的ASCII字符串。

let str = new Blob(['some thing'])
console.log(btoa(str))  // W29iamVjdCBCbG9iXQ==
let dataURL = 'data:text/plain;base64,' + btoa(str) // data:text/plain;base64,W29iamVjdCBCbG9iXQ==

第三种、通过FileReader.readAsDataURL(blob)方法

对于FileBlob对象,可以使用FileReader.readAsDataURL()的方法转换为data:URLs

示例:

const blob = new Blob(['some thing'])
const reader = new FileReader()
reader.onloadend = function() {
  const dataUrl = is_chrome_ios 
            ? reader.result
                    : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;')
}
reader.readAsDataURL(blob)
生成BlobURLs

blob: URLs是URL.createObjectURL() 静态方法创建的一个 DOMString,其中包含一个表示参数中给出的对象的URL。URL.createObjectURL()方法只能处理File或Blob对象,所以如果要生成blobURLs则必须将数据转换为blob对象或file对象。

如果数据不是File或Blob对象
const blob = new Blob([data][, MIMEType])
生成BlobURLs
const BlobURL = URL.createObjectURL(blob)

创建出来的BlobURLs需要手动调用URL.revokeObjectURL()销毁,否则会一直保留到页面关闭,为了获得最佳性能和内存使用状况,你应该在安全的时机主动释放掉它们。

处理下载(或叫导出)方式

第一种、标签的download和href

其中标签的download是HTML5标准新增的属性,作用是指示浏览器下载URL而不是导航到URL,因此将提示用户将其保存为本地文件。另外,download属性的值可以指定下载文件的名称。

href则支持dataURLs和blobURLs两种类型的值。

示例

<a download="filename" href="dataURLs或BlobURLs"></a>

第二种、location.href或window.open()

这个方法是直接把 DataURLs 或者 BlobURLs 传到浏览器地址中触发下载。有两种方式:

window.location.href = urls; // 本窗口打开下载
window.open(urls, '_blank'); // 新开窗口下载

第三种、msSaveOrOpenBlob(IE10+)

这是 IE 特有的方法。

navigator.msSaveOrOpenBlob(blob, fileName);

第四种、iframe(IE <= 9)

其他更现代的浏览器也支持此方法,不过此方法效率和安全性较低,所以一般只在 IE <= 9 时使用。

var frame = document.createElement("iframe");
​
if ( frame ) {
  document.body.appendChild(frame);
  frame.setAttribute("style", "display:none");
  frame.contentDocument.open("txt/html", "replace");
  frame.contentDocument.write(data); // datastring 类型
  frame.contentDocument.close();
  frame.focus();
​
  frame.contentDocument.execCommand("SaveAs", true, filename);
  document.body.removeChild(frame);
}

优点

  • 减少服务器资源和带宽
  • 只需要前端,增加了前端的可控性

缺点

  • 对于大数据量支持度不好
  • 有兼容性问题

事件对象中鼠标位置

纯CSS设置checkbox样式

先隐藏需要自定义样式的checkbox复选框

input[type=checkbox]{
  visibility:hidden;
}

隐藏掉Checkbox复选框后,添加一个label HTML元素,并设置for属性指向对象checkbox元素。

html结构:

<div class='agree-box'>
  <input type='checkbox' id='switch' name='agree'/>
  <label for='switch'></label>
</div>

CSS结构:

.agree-box{
  position: relative;   //重点是位label标签提供绝对定位参考
  width: 40px;
  height: 20px;
  background-color: #ccc;
  border-radius: 10px;
}
​
input[type=checkbox] {   //隐藏checkbox元素
  visibility: hidden;
}
​
.check-label {
  position: absolute;   //开启绝对定位,方便移动
  top: 0;
  left: 0;
  display: inline-block;
  height: 100%;
  width: 20px;
  transition: all 0.5s;   //添加过渡效果
  border-radius: 10px;
  background-color: red;
}
​
input[type=checkbox]:checked+label {   //重点语句
  left: 20px;
  background-color: green;
}

以上就是完全不借助JS实现自定义样式。

表现:

image.png

自定义打勾的checkbox

HTML结构:

<div class="box">
  <input type="checkbox" name="switch" id="switch">
  <label for="switch"></label>
</div>

CSS结构:

.box {
  width: 25px;
  height: 25px;
}
​
input[type=checkbox] {
  visibility: hidden;
}
​
label {
  position: relative;
  display: inline-block;
  width: 25px;
  height: 25px;
  text-align: center;
  background-color: #eeeeee;
}
​
label::after {
  content: '';
  display: inline-block;
  width: 7px;
  height: 12px;
  border-bottom: 4px solid #323232;
  border-right: 4px solid #323232;
  transform: rotate(45deg);
  opacity: 0;
}
​
input[type=checkbox]:checked+label::after {
  opacity: 1;
}

表现:

image.png