zoukankan      html  css  js  c++  java
  • 记录 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标签(或者组件在多个地方调用)的时候一个定要注意选择器的重复命名问题,
    如果出现重复命名问题后果就是修改数据后视图不会实时更新等后果,很难锁定错误,我本人找两三小时,其实是一个小问题,还是修炼不够啊,继续努力)
  • 相关阅读:
    弹性布局、动画、过渡
    HTML
    数据库对象
    函数
    oracle与PL/SQL安装
    网络编程
    多线程
    联调接口
    vue 全局变量
    vue ajax请求
  • 原文地址:https://www.cnblogs.com/gitwusong/p/13601227.html
Copyright © 2011-2022 走看看