axios文件上传功能+formData

2019-12-10 21:14:11

参考地址 01vue+axios+formData实现文件上传(包含简单的Java后台)

用纯css美化<input type=file/>按钮
vue+axios实现文件上传简单原理以及操作
萌新用vue + axios + formdata 上传文件的爬坑之路
从0开始做一个的Vue图片/ 文件选择(上传)组件[基础向]
vue中利用axios实现文件上传进度实时更新
Vue实现带进度条的文件拖动上传
spring boot 文件上传
阿里巴巴iconfont怎么是正确的使用方式?

实现效果

效果图

1.用html和css画出文件上传组件

参考文章用纯css美化<input type=file/>按钮并且利用里面的样式实现。
其中文章的要点是:
1.1文件上传使用<input type=file/>
1.2组合label标签和<input type=file/>美化文件上传

label好用在于,它可以跟input的click事件关联上,这就实现了语义化解决方案。因此点击这两个元素的任何一个都能得到相同的结果——弹出文件上传选择对话框。

其中隐藏<input type=file/>标签的css为:

       .inputfile {
            width: 0.1px;
            height: 0.1px;
            opacity: 0;
            overflow: hidden;
            position: absolute;
            z-index: -1;
        }

你可能会好奇为什么宽和高会设成0.1px而不是0px,因为在某些浏览器下0宽高将会让<input>元素被tab键忽略。而position: absolute的目的是不干扰随后元素的位置。

1.3html+css实现静态页面代码

<body>
    <div id="app" class="m-5">
        <div class="uploadBox">
            <h3>上传文件</h3>
            <div class="fileBox">
                <input type="file" id="myFile" class="inputfile" @change="handlerUpload($event)">
                <label for="myFile">
                    <i class="iconfont">&#xe632;</i>点击上传本地文件                </label>
            </div>
            <div class="fileInfo">
                <ul class="files">
                    <li v-for="(file, index) in files">
                        {{ file.name }}                    </li>
                </ul>
            </div>
        </div>
    </div></body>

1.4实现效果
1.5icon
在静态页面中引用了阿里的icon库。引入方式可以参考:
阿里巴巴iconfont怎么是正确的使用方式?
详细的步骤为:搜索获取适合的图标->加入购物车->添加至项目->下载->将对应iconfont.css复制至css文件夹下引用即可

页面效果

2.构造form'Data,使用axios上传文件

在项目中使用axios上传文件,记得new一个纯净的axios或者考虑用ajax请求。因为axios在项目估计已经用了全局配置请求头等信息,这里的配置可能被全局请求头拦截,导致请求失败。
2.1构造formData

 let param = new FormData();
 param.append("name", "wiiiiiinney");//通过append向form对象添加数据
 param.append("file", file);//FormData私有类对象,访问不到,可以通过get判断值是否传进去
 console.log(param.get("file"));

2.2以下为全局请求头的配置

      let config = {
        //添加请求头
        headers: { "Content-Type": "multipart/form-data" },
        //添加上传进度监听事件
        onUploadProgress: e => {
          var completeProgress = ((e.loaded / e.total * 100) | 0) + "%";
          this.progress = completeProgress;
        }
      };

2.2axios发送请求

 axios.post('http://127.0.0.1:8778/upload', param, config).then(function (response) { console.log(response); }).catch(function (error) {
 console.log(error);
 });

4.后端接收文件

java接收代码:

@Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true); // 允许cookies跨域
        config.addAllowedOrigin("*");// 允许向该服务器提交请求的URI,*表示全部允许。。这里尽量限制来源域,比如http://xxxx:8080 ,以降低安全风险。。
        config.addAllowedHeader("*");// 允许访问的头信息,*表示全部
        config.setMaxAge(18000L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了

        config.addAllowedMethod("*");// 允许提交请求的方法,*表示全部允许,也可以单独设置GET、PUT等
        /*
        config.addAllowedMethod("HEAD");
        config.addAllowedMethod("GET");// 允许Get的请求方法
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("DELETE");
        config.addAllowedMethod("PATCH");
        */
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }private static final Logger logger = LoggerFactory.getLogger(Application.class);
    @Bean
    MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        factory.setLocation("d:/tmp");
        return factory.createMultipartConfig();
    }

    @RequestMapping(value = "/upload")
    @ResponseBody
    public String upload(@RequestParam("file") MultipartFile file,@RequestParam("name")String name) {
        logger.info("name: "+name);
        if (file.isEmpty()) {
            return "文件为空";
        }
        // 获取文件名
        String fileName = file.getOriginalFilename();
        logger.info("上传的文件名为:" + fileName);
        // 获取文件的后缀名        String suffixName = fileName.substring(fileName.lastIndexOf("."));
        logger.info("上传的后缀名为:" + suffixName);
        // 文件上传路径        String filePath = "d:/roncoo/ttt/";
        // 解决中文问题,liunx 下中文路径,图片显示问题        //fileName = UUID.randomUUID() + suffixName;
        File dest = new File(filePath + fileName);
        // 检测是否存在目录        if (!dest.getParentFile().exists()) {
            dest.getParentFile().mkdirs();
        }
        try {
            file.transferTo(dest);
            return "上传成功";
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "上传失败";
    }

6.将上传信息反馈

6.1给每个文件设置一个进度条
用uploadPercentage记录上传进度,uploadPercentage的变化由axios的progress事件监听计算得出。

                        var item = {
                            name: tFiles[i].name,
                            uploadPercentage: 1,
                            size: this.formatFileSize(tFiles[i].size, 0),
                            uploadStatus: 0
                        }
                        console.log(item)
                        this.files.push(item);

6.2给每个文件设置一个上传状态
uploadStatus记录文件状态。

状态码含义
0初始状态
1上传中
2已上传
-1服务器错误
-2上传文件类型不符合要求
-3上传文件超出限制

6.3检测函数大小的函数

                checkFileSize:function(fileSize) {
                    //2M
                    const MAX_SIZE = 2 * 1024 * 1024;
                    if (fileSize > MAX_SIZE) {
                        return false;
                    }
                    return true;
                }

6.4检测文件类型的函数

                checkFileType: function (fileType) {
                    const acceptTypes = ['xls', 'doc'];
                    for (var i = 0; i < acceptTypes.length; i++) {
                        if (fileType === acceptTypes[i]) {
                            return true;
                        }
                    }
                    return false;
                }

6.4格式化文件大小的函数

               formatFileSize: function (fileSize, idx) {
                    var units = ["B", "KB", "MB", "GB"];
                    idx = idx || 0;
                    if (fileSize < 1024 || idx === units.length - 1) {
                        return fileSize.toFixed(1) +
                            units[idx];
                    }
                    return this.formatFileSize(fileSize / 1024, ++idx);
                },

7.全部代码

<!doctype html><html lang="en"><head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/vue/2.5.13/vue.min.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        li {
            list-style: none;
        }

        /*引入阿里的icon*/

        @font-face {
            font-family: "iconfont";
            src: url('iconfont.eot?t=1534844614970');
            /* IE9*/
            src: url('iconfont.eot?t=1534844614970#iefix') format('embedded-opentype'), /* IE6-IE8 */
            url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAATgAAsAAAAAB1AAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8qEgkY21hcAAAAYAAAABLAAABcOdatkBnbHlmAAABzAAAASwAAAFY2/C2dWhlYWQAAAL4AAAALwAAADYSY0OKaGhlYQAAAygAAAAcAAAAJAfeA4NobXR4AAADRAAAAAgAAAAICAAAAGxvY2EAAANMAAAABgAAAAYArAAAbWF4cAAAA1QAAAAgAAAAIAEPAH1uYW1lAAADdAAAAUUAAAJtPlT+fXBvc3QAAAS8AAAAIwAAADQ9SsrveJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeGT0zYm7438AQw9zA0AAUZgTJAQDk9QxHeJxjYGBgZWBgYAZiHSBmYWBgDGFgZAABP6AoI1icmYELLM7CoARWwwISf2b0/z+MBPJZwCQDIxvDKOABkzJQHjisIJiBEQCjLgoxAHicHY3BTsJAGIR3drv/0hZaqXVLIKJSugVNQAOUxCKYeNKLMTHRB+Bi4sGTD2B8GV8C4zNxJepC/vxzmMzMxwRjfz/iW1wzn52xN8YQQLWRzDEdQJA60Co1C5gJhSAzLbZ3BB1TZwgjY2XTaceMixK2UZi8Y2u5Iesb+3ZIl9CJppTyAYaYFFObG21XEp3EI30EzlYbKTernX6B89ALAAFuvARwsiwI7sgPyXBo7p6ffLqe737Eh1K2Wxrifn65bKhKFjXbTtpS6rUu4LvlrFcSwL2y59Dpy/vjQMmZB7HYgRxnq78PwDqSMlpbHM9cv6pk1q3Vbin0KeNdomMDr+4h2W/2uF48+2o03kvhVPpF5gChW7WEoi8SonyIaNloPV3cyPoVZ+wfIBIx1XicY2BkYGAA4r3qZgbx/DZfGbhZGEDg+sL/xxD0/4MsDMwOQC4HAxNIFAA6yguGAHicY2BkYGBu+N/AEMPCAAJAkpEBFTABAEcIAmsEAAAABAAAAAAAAAAArAAAAAEAAAACAHEAAwAAAAAAAgAAAAoACgAAAP8AAAAAAAB4nGWPTU7DMBCFX/oHpBKqqGCH5AViASj9EatuWFRq911036ZOmyqJI8et1ANwHo7ACTgC3IA78EgnmzaWx9+8eWNPANzgBx6O3y33kT1cMjtyDRe4F65TfxBukF+Em2jjVbhF/U3YxzOmwm10YXmD17hi9oR3YQ8dfAjXcI1P4Tr1L+EG+Vu4iTv8CrfQ8erCPuZeV7iNRy/2x1YvnF6p5UHFockikzm/gple75KFrdLqnGtbxCZTg6BfSVOdaVvdU+zXQ+ciFVmTqgmrOkmMyq3Z6tAFG+fyUa8XiR6EJuVYY/62xgKOcQWFJQ6MMUIYZIjK6Og7VWb0r7FDwl57Vj3N53RbFNT/c4UBAvTPXFO6stJ5Ok+BPV8bUnV0K27LnpQ0kV7NSRKyQl7WtlRC6gE2ZVeOEXpc0Yk/KGdI/wAJWm7IAAAAeJxjYGKAAC4G7ICJkYmRmYGrOCMxLz05ozQxj4EBACJZBEAA') format('woff'), url('iconfont.ttf?t=1534844614970') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
            url('iconfont.svg?t=1534844614970#iconfont') format('svg');
            /* iOS 4.1- */
        }

        .iconfont {
            font-family: "iconfont" !important;
            font-size: 16px;
            font-style: normal;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
        }

        .icon-shangchuan:before {
            content: "\e632";
        }


        .iconfont {
            font-family: "iconfont";
            font-size: 18px;
            font-style: normal;
            -webkit-font-smoothing: antialiased;
            -webkit-text-stroke-width: 0.2px;
            -moz-osx-font-smoothing: grayscale;
            padding-left: 20px        }

        .uploadBox {
            width: 400px;
            border: 1px solid #ccc;
            margin: 100px auto;
        }

        .fileBox,
        .fileInfo {
            margin: 16px;
            height: 60px;
            line-height: 60px;
            border: 1px solid #ccc;
            padding-left: 16px;
            font-size: 16px;
        }

        .inputfile {
            width: 0.1px;
            height: 0.1px;
            opacity: 0;
            overflow: hidden;
            position: absolute;
            z-index: -1;
        }

        /*E + F 毗邻元素选择器,匹配所有紧随E元素之后的同级元素F*/

        .inputfile+label {
            color: #3e97df;
            display: inline-block;
        }

        .inputfile:focus+label,
        .inputfile+label:hover {
            color: #0c89f0;
        }

        h3 {
            padding: 10px 0 0 16px;
            font-weight: normal;
            font-size: 18px;
            color: #666;
        }

        .filePart {
            line-height: 30px;
            overflow: hidden;
            float: left;
            text-overflow: ellipsis;
            white-space: nowrap;
            font-size: 12px;
            height: 30px;
        }

        .fileStatus {
            overflow: hidden;
            float: left;
            height: 20px;
            font-size: 10px;
            line-height: 20px;
        }

        .ml10 {
            margin-left: 10px;
        }

        .fileName {
            width: 200px;
        }

        .fileSize {
            width: 120px;
            text-align: center;
        }

        .uploadFail {
            color: #ff0800d3;
        }

        .uploadSuccess {
            color: #2c832c;
        }

        /*对应CSS*/

        .progress {
            position: relative;
            width: 80%;
            height: 8px;
            border: 1px solid #ccc;
            border-radius: 5px;
            overflow: hidden;
            /*注意这里*/
            box-shadow: 0 0 1px 0px #ddd inset;
        }

        .progress span {
            position: absolute;
            display: inline-block;
            width: 10%;
            height: 100%;
            background-color: #3e97df;
        }
    </style>
    <title>Document</title></head><body>
    <div id="app" class="m-5">
        <div class="uploadBox">
            <h3>上传文件</h3>
            <div class="fileBox">
                <input type="file" id="myFile" class="inputfile" @change="handlerUpload($event)">
                <label for="myFile">
                    <i class="iconfont">&#xe632;</i>点击上传本地文件                </label>
            </div>
            <ul class="files">
                <li v-for="(file, index) in files">
                    <div class="fileInfo">

                        <div class="fileName filePart">
                            {{ file.name }}                        </div>
                        <div class="fileSize filePart ml10">
                            {{file.size}}                        </div>
                        <!--进度条-->
                        <div class="progress">
                            <span :style="{width:file.uploadPercentage,backgroundColor:file.uploadStatus==1 ||file.uploadStatus==2?'':'red'}"></span>
                        </div>
                        <div class="fileStatus">
                            <span v-if="file.uploadStatus == -1" class="uploadFail">出错啦,请重新上传或者删除</span>
                            <span v-if="file.uploadStatus == 2" class="uploadSuccess"> 已上传</span>
                            <span v-if="file.uploadStatus == 1" class="uploadSuccess"> 上传中...</span>
                            <span v-if="file.uploadStatus == -2" class="uploadFail">出错啦,文件类型不符合要求</span>
                            <span v-if="file.uploadStatus == -3" class="uploadFail">出错啦,文件大小超出限制</span>
                        </div>
                    </div>
                </li>
            </ul>
        </div>
    </div>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                files: [],
                uploadSuccess: 0
            },
            methods: {
                handlerUpload: function (e) {
                    //获取选定的文件
                    let tFiles = e.target.files;
                    let len = tFiles.length;
                    for (var i = 0; i < len; i++) {
                        //开始上传每一个文件
                        var item = {
                            name: tFiles[i].name,
                            uploadPercentage: 1,
                            size: this.formatFileSize(tFiles[i].size, 0),
                            uploadStatus: 0
                        }
                        console.log(item)
                        this.files.push(item);
                        //开始上传文件 新建一个formData
                        let param = new FormData();
                        param.append("name", "wiiiiiinney");
                        //通过append向form对象添加数据
                        param.append("file", tFiles[i]);
                        //FormData私有类对象,访问不到,可以通过get判断值是否传进去
                        console.log(param.get("file"));
                        //判断大小
                        if (!this.checkFileSize(tFiles[i].size)) {
                            item.uploadStatus = -3;
                            return false;
                        }
                        if (!this.checkFileType(tFiles[i].name.split('.')[1])) {
                            item.uploadStatus = -2;
                            return false;
                        }
                        //通过axios上传文件
                        //配置
                        let config = {
                            //添加请求头 
                            headers: {
                                "Content-Type": "multipart/form-data"
                            },
                            //添加上传进度监听事件 
                            onUploadProgress: e => {
                                var completeProgress = ((e.loaded / e.total * 100) | 0) + "%";
                                console.log(this.files)
                                item.uploadPercentage = completeProgress;
                            }
                        };
                        axios.post('http://127.0.0.1:8778/upload', param, config).then(function (
                            response) {
                            console.log(response);
                            item.uploadStatus = 2;
                        }).catch(function (error) {
                            console.log(error);
                            item.uploadStatus = -1;
                        });
                    }
                },
                formatFileSize: function (fileSize, idx) {
                    var units = ["B", "KB", "MB", "GB"];
                    idx = idx || 0;
                    if (fileSize < 1024 || idx === units.length - 1) {
                        return fileSize.toFixed(1) +
                            units[idx];
                    }
                    return this.formatFileSize(fileSize / 1024, ++idx);
                },
                checkFileType: function (fileType) {
                    const acceptTypes = ['xls', 'doc', 'jpg'];
                    for (var i = 0; i < acceptTypes.length; i++) {
                        if (fileType === acceptTypes[i]) {
                            return true;
                        }
                    }
                    return false;
                },
                checkFileSize: function (fileSize) {
                    //2M
                    const MAX_SIZE = 2 * 1024 * 1024;
                    if (fileSize > MAX_SIZE) {
                        return false;
                    }
                    return true;
                }
            }
        });
    </script></body></html>

8.参考代码、使用ajax上传文件、实现拖拽功能

<!doctype html><html lang="en"><head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/vue/2.5.13/vue.min.js"></script>
    <style>
        .dropbox {
            border: .25rem dashed #007bff;
            min-height: 5rem;
        }
    </style>
    <title>Document</title></head><body>
    <div id="app" class="m-5">
        <div class="dropbox p-3">
            <h2 v-if="files.length===0" class="text-center">把要上传的文件拖动到这里</h2>
            <div class="border m-2 d-inline-block p-4" style="width:15rem" v-for="file in files">
                <h5 class="mt-0">{{ file.name }}</h5>
                <div class="progress">
                    <div class="progress-bar progress-bar-striped" :style="{ width: file.uploadPercentage+'%' }"></div>
                </div>
            </div>
        </div>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                files: []
            },
            methods: {
                uploadFile: function (file) {
                    var item = {
                        name: file.name,
                        uploadPercentage: 0
                    };
                    this.files.push(item);
                    var fd = new FormData();
                    fd.append('myFile', file);

                    var xhr = new XMLHttpRequest();
                    xhr.open('POST', 'http://127.0.0.1:8778/upload', true);
                    xhr.upload.addEventListener('progress', function (e) {
                        item.uploadPercentage = Math.round((e.loaded * 100) / e.total);
                    }, false);
                    xhr.send(fd);
                },
                onDrag: function (e) {
                    e.stopPropagation();
                    e.preventDefault();
                },
                onDrop: function (e) {
                    e.stopPropagation();
                    e.preventDefault();
                    var dt = e.dataTransfer;
                    for (var i = 0; i !== dt.files.length; i++) {
                        this.uploadFile(dt.files[i]);
                    }
                }
            },
            mounted: function () {
                var dropbox = document.querySelector('.dropbox');
                dropbox.addEventListener('dragenter', this.onDrag, false);
                dropbox.addEventListener('dragover', this.onDrag, false);
                dropbox.addEventListener('drop', this.onDrop, false);
            }
        });
    </script></body></html>



  • 2019-12-06 10:47:29

    date-fns日期工具的使用方法详解

    isToday() 判断传入日期是否为今天 isYesterday() 判断传入日期是否为昨天 isTomorrow() 判断传入日期是否为 format() 日期格式化 addDays() 获得当前日期之后的日期 addHours() 获得当前时间n小时之后的时间点 addMinutes() 获得当前时间n分钟之后的时间 addMonths() 获得当前月之后n个月的月份 subDays() 获得当前时间之前n天的时间 subHours() 获得当前时间之前n小时的时间 subMinutes() 获得当前时间之前n分钟的时间 subMonths() 获得当前时间之前n个月的时间 differenceInYears() 获得两个时间相差的年份 differenceInWeeks() 获得两个时间相差的周数 differenceInDays() 获得两个时间相差的天数 differenceInHours() 获得两个时间相差的小时数 differenceInMinutes() 获得两个时间相差的分钟数

  • 2019-12-06 10:49:39

    npm 查看源 换源

    npm,cnpm,查看源,切换源,npm config set registry https://registry.npmjs.org

  • 2019-12-06 11:01:31

    npm发布包流程详解 有demo

    npm发布包步骤,以及踩过的坑(见红颜色标准): 1.注册npm账号,并完成Email认证(否则最后一步提交会报Email错误) 2.npm添加用户或登陆:npm adduser 或 npm login

  • 2019-12-06 13:16:18

    vue mixins组件复用的几种方式

    最近在做项目的时候,研究了mixins,此功能有妙处。用的时候有这样一个场景,页面的风格不同,但是执行的方法,和需要的数据非常的相似。我们是否要写两种组件呢?还是保留一个并且然后另个一并兼容另一个呢? 不管以上那种方式都不是很合理,因为组件写成2个,不仅麻烦而且维护麻烦;第二种虽然做了兼容但是页面逻辑造成混乱,必然不清晰;有没有好的方法,有那就是用vue的混合插件mixins。混合在Vue是为了提出相似的数据和功能,使代码易懂,简单、清晰。

  • 2019-12-06 13:26:30

    vue的mixins混入合并规则

    混入minxins:分发vue组件中可复用功能的灵活方式。混入对象可以包含任意组件选项。组件使用混入对象时,所有混入对象的选项将混入该组件本身的选项。

  • 2019-12-06 16:50:34

    Intellij idea 如何关闭无用的提示

    Linux:Settings —> Editor —> Inspections —> General —> Duplicated Code Mac:Preferences --> Editor —> Inspections —> General —> Duplicated Code fragment 将对应的勾去掉。

  • 2019-12-09 15:36:56

    神秘的 shadow-dom 浅析,shadow-root

    顾名思义, shadow-dom,直译的话就是 影子dom ?我觉得可以理解为潜藏在黑暗中的 DOM 结构,也就是我们无法直接控制操纵的 DOM 结构。前端同学经常用开发者工具的话,查看 DOM 结构的时候,肯定看到过下面这样的结构:

  • 2019-12-10 11:13:50

    前端实战-基于Nuxt的SVG使用

    虽然我们在日常开发的时候,在使用iview 或者element ui等组件时,通常会包含一些常用icon;但是在面对一些特定的需求时,或者自己想high一下,这些通用的icon并不能很好的满足我们。这个时候我们可能会拿到一些SVG适量图,但是怎么去使用这些矢量图呢。

  • 2019-12-10 11:15:08

    用CSS给SVG 的内容添加样式

    SVG图形的一个最常见用例是图标系统,其中最常用的SVG sprite技术就是使用SVG<use> 元素在文档中任意位置“实例化”图标。 使用<use>元素实例化图标或任何其它的SVG元素或图像,给元素添加样式时经常会碰到一些问题。这篇文章的目的是尽可能给你介绍一些方法来解决:使用<use>引入的内容添加样式受限的问题。 但是在开始之前,我们先快速浏览一下SVG的主要结构和分组元素,然后慢慢进入use的世界中,以及shadow DOM,然后重回CSS的怀抱。我们会逐步讲解为什么给<use>内容添加样式会比较麻烦,以及有什么好的解决方案。