记录 vue 中使用 SVG 渐变填充遇到过的坑

需求是一个vue组件封装 通过弹框填写相应属性(弹框做属性填写和选择 加属性预览) 点击确定后关闭弹框拿到返回的数据更新状态树

  代码:vue部分

<template>
    <div class="fillDialog">
        <div class="mask" @click="cancel"></div>
        <div class="fillDialogContent">
            <div class="title">
                编辑填充
                <img src="/images/close.png" class="img" @click="cancel"/>
            </div>
            <!-- 内容区 -->
            <div class="content">
                <div class="content_left">
                   <ul class="ul">
                       <li class="li" @click="fill_.fillColor.value = 'none';fill_.fillGradientColor.value = 'color'">
                           <div class="lititle">
                               <img src="../../assets/selected.png" alt="" v-if="fill_.fillColor.value == 'none'">
                               <img src="../../assets/select.png" alt="" v-else>
                               <span >无填充</span>
                            </div>
                       </li>
                       <li class="li">
                           <div class="lititle" @click="fill_.filColor.value = ''">
                               <img src="../../assets/select.png" alt="" v-if=" fill_.fillColor.value == 'none'">
                               <img src="../../assets/selected.png" alt="" v-else>
                               <span>填充</span>
                            </div>
                            <div v-if=" fill_.fillColor.value !== 'none'" class="liContent">
                                <div class="liItem">
                                    <label for="">透明度</label>
                                    <select name="" id="" v-model="fill_.fillOpacity.value">
                                        <option 
                                        v-for="(item,index) in fill_.fillOpacity.range"
                                        :key="index"
                                        :value="item"
                                        >{{item}}</option>
                                    </select>
                                </div>
                                <div class="liItem">
                                    <label for="">颜色选择</label>
                                    <select name="" id="" v-model="fill_.fillGradientColor.value">
                                        <option value="#linearCol">纵向线性渐变</option>
                                        <option value="#linearRow">横向线性渐变</option>
                                        <option value="#radial">径向渐变</option>
                                        <option value="color">填充色</option> 
                                    </select>
                                    <dl v-if="fill_.fillGradientColor.value !== 'color'">
                                        <dd 
                                         v-for="(v,i) in fill_.fillGradientColorGroup.value"
                                         :key="i"
                                        >
                                            <label for="">{{v.label+ '%'}}</label>
                                            <input type="color"  v-model="v.value">
                                            <button style="margin-left:20px;" @click="deleteColor(i)" :disabled="v.label == 0 || v.label == 100">删除</button>
                                        </dd>
                                        <dd >
                                            <button @click="messageStatus = !messageStatus" style="color:#0000ff;">点我</button>添加更多颜色
                                        </dd>
                                        <dd v-if="messageStatus" style="border:1px solid #333;60%;padding:10px;">
                                            <div>
                                                <label for="">位置</label>
                                                <input type="number" id="" style="50px"  v-model="addData.label" min="0" max="100">%
                                            </div>
                                            <div>
                                                <label for="">颜色</label>
                                                <input type="color" name="" id="" v-model="addData.value">
                                            </div>
                                            <button @click="addColor">添加</button>
                                        </dd>
                                    </dl>
                                    <div  v-else>
                                        <label for="">填充色</label>
                                        <input type="color" name="" id="" v-model="fill_.fillColor.value">
                                    </div>
                                </div>
                            </div>
                       </li>
                   </ul>
                </div>
                <!-- 预览区 -->
                <div class="content_right">
                    <svg 
                    style="position:absolute;margin:auto;left:0;right:0;left:0;bottom:0;top:0;"
                    width="200"
                    height="200"
                    viewBox="0 0 200 200"
                    >
                        <linearGradient id="linearRow_dialog" x1="0%" y1="0%" x2="0%" y2="100%" >
                            <stop 
                                v-for="(itm,idx) in fill_.fillGradientColorGroup.value"
                                :key="idx"
                                :offset="itm.label +'%'" 
                                :stop-color="itm.value" 
                                :stop-opacity="fill_.fillOpacity.value"
                            />
                        </linearGradient>
                        <linearGradient id="linearCol_dialog" x1="0%" y1="0%" x2="100%" y2="0%" >
                            <stop 
                                v-for="(itm,idx) in fill_.fillGradientColorGroup.value"
                                :key="idx"
                                :offset="itm.label +'%'" 
                                :stop-color="itm.value" 
                                :stop-opacity="fill_.fillOpacity.value"
                            />
                        </linearGradient>
                        <radialGradient id="radial_dialog" fx="50%" fy="50%" cx="50%" cy="50%" r="50%"> 
                            <stop 
                            v-for="(itm,idx) in fill_.fillGradientColorGroup.value"
                            :key="idx"
                            :offset="itm.label +'%'" 
                            :stop-color="itm.value" 
                            :stop-opacity="fill_.fillOpacity.value" /> 
                        </radialGradient>

                        <ellipse cx="100" cy="100" rx="100" ry="100" 
                        :style="`fill:${fill_.fillGradientColor.value =='color' ?  fill_.fillColor.value : 'url('+fill_.fillGradientColor.value+'_dialog'+')'}`"
                        stroke="#333333"
                        stroke-width='1'
                        />
                    </svg>
                </div>
            </div>
            <div class="btn-group">
                <div class="btn-default btn" @click="cancel">取消</div>
                <div class="btn-primary  btn" @click="confirm">确定</div>
            </div>
        </div>
    </div>
</template>
<script>

export default {
    props:{
        fill:{
            type:Object,
            default:() => ({})
        },
    },
    data  ()  {
        let fill_ = JSON.parse(JSON.stringify(this.fill))
            fill_.fillOpacity.range = [0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1]
        return {
            fill_:fill_,
            messageStatus:true,
            addData:{
                label:"",
                value:"#000000"
            },
            gradientStatus:true
        }
    },
    mounted () {
        this.messageStatus = false
    } ,
    methods: {
        addColor () {
            let status = true
            for (let index = 0; index <  this.fill_.fillGradientColorGroup.value.length; index++) {
               if(this.fill_.fillGradientColorGroup.value[index].label == this.addData.label ) {
                   status = false
               }
            }
            if (status && this.addData.label && this.addData.value && this.addData.label < 100 && this.addData.label > 0) {
                   this.fill_.fillGradientColorGroup.value.push(JSON.parse(JSON.stringify(this.addData)))
                   this.fill_.fillGradientColorGroup.value =  this.fill_.fillGradientColorGroup.value.sort((a,b) => a.label - b.label)
            } else {
              if (!status) {
                  alert("位置已存在")
              }else if (!this.addData.label) {
                  alert("位置未填写")
              }else if (!this.addData.value) {
                  alert("颜色未填写")
              }else if (this.addData.label >= 100) {
                  alert("位置不能大于100")
              }else if (this.addData.label <= 0) {
                  alert("位置不能小于0")
              }
            }
        },
        deleteColor (index) {
            this.fill_.fillGradientColorGroup.value.splice(index,1)
        },
        confirm () {
            this.resolve(this.fill_);
            this.remove();
        },
        cancel () {
            this.reject('cancel');
            this.remove();
        },
        showFillDialog () {
            this.promise = new Promise((resolve, reject) => {
                this.resolve = resolve;
                this.reject = reject;
            });
            return this.promise;
        },

        remove () {
            setTimeout(() =>  this.destroy() ,0);
        },
        destroy() {
            this.$destroy();
            document.body.removeChild(this.$el);
        }
    }
};
</script>
<style lang="less" scoped>

    @height:160px;
    @padding-top:60px;
    .fillDialog {
        position: fixed;
        top:0;
        left:0;
        bottom:0;
        right:0;
        z-index:999999;
        margin:auto;
        .mask {
            100%;
            height:100%;
            background-color:rgba(0, 0, 0, 0.5)
        }
        .fillDialogContent {
            background-color:rgba(255, 255, 255, 1);
            overflow: hidden;
            border-radius:10px;
            display:flex;
            flex-direction: column;
            40%;
            height:60%;
            position:absolute;
            top:0;
            left:0;
            bottom:0;
            right:0;
            margin:auto;
            .title {
                text-align:center;
                100%;
                height:50px;
                line-height: 50px;
                font-size:16px;
                background-color:#6550b1;
                color:#fff;
                position: relative;
                .img{
                    position:absolute;
                    top:18px;right:18px;
                    cursor:pointer;
                }
            }
            .content {
                flex:1;
                padding:10px;
                100%;
                height:@height;
                font-size:14px;
                line-height:30px;
                display:flex;
                justify-content: space-around;
                box-sizing:border-box;
                .content_left{
                    box-sizing:border-box;
                    70%;
                    height:100%;
                    .ul{
                        height:100%;
                        overflow-y:auto;
                        .li{
                            min-height:30px;
                            line-height:30px;
                            .lititle{
                                display:flex;
                                img{
                                    margin-top:5px;
                                    20px;
                                    height:20px;
                                }
                                span{
                                    flex:1;
                                }
                            }
                            .liContent{
                                min-height:50px;
                                box-sizing: border-box;
                                padding-left:30px;
                                .liItem{
                                    >label{
                                        80px;
                                        display:inline-block;
                                        text-align:justify;
                                        text-align-last: justify;
                                        margin-right:20px;
                                    }
                                }
                            }
                        }
                    } 
                }
                .content_right{
                    box-sizing:border-box;
                    30%;
                    height:100%;
                    border:1px solid #333;
                    text-align:center;
                    overflow: hidden;
                    position:relative;
                }   
            }
            .btn-group {
                100%;
                height:50px;
                line-height:50px;
                border-top:1px solid #f1f1f1;
                position: relative;
                .btn{
                    position: absolute;
                    top:10px;
                    height:30px;
                    50px;
                    line-height:30px;
                    display:inline-block;
                    padding:0 10px;
                    font-size:14px;
                    border-radius:3px;
                    background:rgba(0, 0, 0, 0.3);
                    text-align: center;
                    cursor: pointer;
                }
                .btn-default {
                    right:110px;
                }
                .btn-primary {
                    right:20px;
                }
                .btn:hover{
                    background-color:salmon;
                }
                .btn:active{
                    background-color:sienna;

                }
            }
        }
    }
</style>

代码 : js 部分 

import fillDialogComponent from './index.vue';    
const MessageBox = {};
MessageBox.install = function (Vue) {

  const fillDialog = Vue.extend(fillDialogComponent);
  let currentFillDialogIstance = null;
  Vue.prototype.showFillDialog = function(fill) {

    if (!currentFillDialogIstance) {
      currentFillDialogIstance = new fillDialog({
          propsData:{fill}
        }
      );

      let msgBoxEl = currentFillDialogIstance.$mount().$el;
      document.body.appendChild(msgBoxEl);
    }
    else {
      Object.assign(currentFillDialogIstance, {fill})
    }
    
    return currentFillDialogIstance.showFillDialog()
      .then(val => {
        currentFillDialogIstance = null;
        return Promise.resolve(val);
      })
      .catch(err => {
        currentFillDialogIstance = null;
        return Promise.reject(err);
      });
  }
};

export default MessageBox;

 

代码 : main.js 部分

import FillDialog from "@/dialog/filDialog.js"
Vue.use(FillDialog)
然后就可以vm.showFillDialog()调用

其中遇到一个坑找了两三个小时才找到 (也不算是坑 主要是对SVG不太熟悉,记录一下)
svg标签内 linearGradient、radialGradient在通过id绑定ellipse fill的url地址时 会被全局编辑(就是id选择器在全局中只能有一个,当出现多个有linearGradient、radialGradient的svg标签(或者组件在多个地方调用)的时候一个定要注意选择器的重复命名问题,
如果出现重复命名问题后果就是修改数据后视图不会实时更新等后果,很难锁定错误,我本人找两三小时,其实是一个小问题,还是修炼不够啊,继续努力)
原文地址:https://www.cnblogs.com/gitwusong/p/13601227.html