vue监听window.resize

1.问题


今天在项目中遇到一个问题,使用eCharts图表时,在窗口改变大小时,需要重新渲染图表,我将eCharts封装成了组件,在组件中监听window.onresize,但是效果却不与预期相符,一个页面中有多个eChart组件,但是只有最后一个组件的监听生效,其他的都没有生效。

但是这个插件是我从之前的项目中直接拿过来用的,唯一不同的地方是,之前的项目引入了jQquary,监听使用$(window).resize()方法,这个监听是没有问题的。

 

2.分析


window.onresize 是直接给window的onresize属性绑定事件,只能有一个,也就是说在项目中 window.onresize只能挂载一次,在多个页面中同时挂载 window.onresize时,只有其中一个 window.onresize会起作用,前面的会被后面的覆盖 ,在路由中同样如此,后面的会覆盖前面的,父子组件中,父组件会覆盖子组件的。

而jQuery的用法 $(window).resize()可以写多个方法,有时我们可能想要处理比较复杂的逻辑,会对性能影响较大,这样就比较容易造成浏览器假死。

做一个防抖处理,通过增加定时器的方式来让代码延迟执行,这样每次窗口改变的时候,我们都清除事件,只有当他停下来之后,才会继续执行。这个方法虽然可以解决resize执行多次的问题,但是感觉还不够完美。比如有些情况,我们需要窗口改变后立即在页面上做一些变化,这种方法并不适用

 

3.解决


如果一个页面中有多个需要监听窗口改变的,那就放到父组件中吧,然后传值给子组件,子组件监听传入的值然后做出相应的操作。

代码如下:

1.vue组件

<!--
  /**
     * @author: wendell_chen
     * @createDate: 2019/5/31
     * @weChat: fourteen_clever
     * **/
-->
<template>
  <div :id="`eChart${id}`" class="chartsWrap"></div>
</template>

<script>
  export default {
    name: "EChart",
    props: {
      option: {
        type: Object
      },
      id: {
        type: [Number, String]
      },
      resize: {
        type: Boolean
      }
    },
    data() {
      return {
        eChart: null,
        timer: null
      }
    },
    watch: {
      option: {
        handler(newValue, oldValue){
          if(this.eChart){
            this.eChart.setOption(this.option, {
              notMerge: true
            });
          }else {
            this.initChart();
          }
        },
        deep: true
      },
      resize(newValue, oldValue){
        this.eChart.clear();
        this.eChart.dispose();
        this.eChart = this.$echarts.init(document.getElementById(`eChart${this.id}`));
        this.eChart.setOption(this.option);
      }
    },
    mounted() {
      this.initChart();
      this.initResize();
    },
    beforeDestroy() {
      if(this.eChart){
        this.eChart.clear();
        this.eChart.dispose();
        this.eChart = null;
       }
       this.removeResize();
    },
    methods: {
      // 初始化
      initChart(){
        if(this.eChart){
          this.eChart.clear();
          this.eChart.setOption(this.option);
        }else {
          this.eChart = this.$echarts.init(document.getElementById(`eChart${this.id}`));
          this.eChart.setOption(this.option);
        }
      },

      // 注册resize方法
      initResize(){
        if(this.resize === undefined){
          window.addEventListener('resize', this.resizeFunction, false);
        }
      },

      // 移除resize方法
      removeResize(){
        if(this.resize === undefined){
          window.removeEventListener('resize', this.resizeFunction);
        }
      },

      // resize时需要执行的函数
      resizeFunction(){
        if(this.timer){
         clearTimeout(this.timer);
        }

      this.timer = setTimeout(() => {
        if(this.eChart && document.getElementById(`eChart${this.id}`)){
          this.eChart.clear();
          this.eChart.dispose();
          this.eChart = this.$echarts.init(document.getElementById(`eChart${this.id}`));
          this.eChart.setOption(this.option);
        }
      })
    }
  }
}
</script>

<style lang="scss">
  @import "./eChart";
</style>

2.使用组件

<!--
  /**
     * @author: wen_dell
     * @createDate: 2019/12/11
     * @weChat: fourteen_clever
     * **/
-->
<template>
    <div id="home" class="home_wrap">
        <el-row :gutter="6" class="home_row">
            <el-col :span="4" class="home_col">
                <ul class="item_ul">
                    <li class="item_li">
                        <div class="item_li_content">
                            <p class="item_date">2017/7/17 星期三</p>
                            <p class="item_aqi">
                                <span>34</span> 良 AQI<span class="ml">20/cm³</span> 负氧离子
                            </p>
                            <p class="item_weather">
                                <i class="el-icon-sunny weather_img"></i>
                                <span class="item_weather_detail">
                                    26℃~34℃<br />东北风 3-4级
                                </span>
                            </p>
                        </div>
                    </li>
                    <li v-for="item in chartOptions.slice(0, 4)" :key="item.id" class="item_li">
                        <div class="item_li_content">
                            <e-chart :id="item.id" :option="item.option" :resize="item.resize"></e-chart>
                        </div>
                    </li>
                </ul>
            </el-col>
            <el-col :span="16" class="home_col home_col_center">
                <div class="home_col_map">
                    <h5 class="home_col_map_title">经开区精准扶贫看板</h5>
                    <img class="home_map" src="../../assets/image/home-map.png" alt="">

                    <area-info
                        v-model="checkedArea"
                        class="home_address"
                        @check="checkArea"
                    ></area-info>
                </div>
                <div class="home_col_center_bottom">
                    <div class="home_col_chart">
                        <e-chart :id="chartOptionBottom.id" :option="chartOptionBottom.option" :resize="chartOptionBottom.resize"></e-chart>
                    </div>
                </div>
            </el-col>
            <el-col :span="4" class="home_col">
                <ul class="item_ul">
                    <li class="item_li">
                        <div class="item_li_content">
                            <div class="item_li_right_label">
                                设备总数
                                <span>500</span>
                            </div>
                            <div class="item_li_right_label">
                                智能床垫
                                <span>500</span>
                            </div>
                            <div class="item_li_right_label">
                                智能手环
                                <span>500</span>
                            </div>
                            <div class="item_li_right_label">
                                报警终端
                                <span>500</span>
                            </div>
                        </div>
                    </li>
                    <li v-for="item in chartOptions.slice(4, 8)" :key="item.id" class="item_li">
                        <div class="item_li_content">
                            <e-chart :id="item.id" :option="item.option" :resize="item.resize"></e-chart>
                        </div>
                    </li>
                </ul>
            </el-col>
        </el-row>
    </div>
</template>

<script>
    import EChart from '../../components/echart/EChart';
    import Area from '@/components/area/Area';
    import { chartOption } from '../../utils/constant';
    export default {
        name: "Dashboard",
        components: {
            'e-chart': EChart,
            'area-info': Area
        },
        data(){
            return{
                map: null,
                chartOptions: [],

                chartOptionBottom: {
                    id: 9,
                    resize: false,
                    option: {}
                },
                checkedArea: [],
                timer: null,
            }
        },
        mounted() {
            this.initChart();

            this.getHomeData();

            this.initResize();
        },
        beforeRouteLeave(to, from, next){
            Object.assign(this.$data, this.$options.data());
            this.removeResize();
            next();
        },
        methods: {
            initChart(){
                for(let i = 0; i < 8; i++){
                    this.chartOptions[i] = {
                        id: i,
                        resize: false,
                        option: Object.assign({}, JSON.parse(JSON.stringify(chartOption)))
                    }
                }
            },

            checkArea(){
                this.showArea = !this.showArea;
            },

            getHomeData(){
                this.chartOptions[0].option.title.text = '经开区家庭医生12个月数量增长';
                this.chartOptions[0].option.xAxis[0].data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
                this.chartOptions[0].option.series[0].data = [1, 2, 3, 4, 5, 6, 7, 2, 3, 5, 4, 8];

                this.chartOptions[1].option.title.text = '经开区家庭医生12个月帮扶人数增长';
                this.chartOptions[1].option.xAxis[0].data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
                this.chartOptions[1].option.series[0].data = [1, 0, 1, 0, 5, 3, 7, 2, 3, 5, 4, 8];

                this.chartOptions[2].option.title.text = '经开区家庭医生服务覆盖率';
                this.chartOptions[2].option.color = [ 'rgb(0, 165, 255)', 'rgb(173, 226, 255)'];
                this.chartOptions[2].option.series[0] = {
                    name: '访问来源',
                    type: 'pie',
                    selectedOffset: 0,
                    selectedMode: 'single',
                    radius: ['55%', '70%'],
                    center: ['50%', '60%'],
                    label: {
                        normal: {
                            show: true,
                            position: 'center',
                            formatter: (params) => {
                                return (params.dataIndex === 0 ? '{num|'+ params.name + '}' : '');
                            },
                            rich: {
                                num: {
                                    fontSize: 23,
                                    fontWeight: 600,
                                    color: '#1facff',
                                },
                                pre: {
                                    fontSize: 12,
                                    color: '#1facff',
                                }
                            },
                            color: '#1facff',
                            fontWeight: 600,
                            fontSize: 20
                        },
                    },
                    labelLine: {
                        normal: {
                            show: false
                        }
                    },
                    data:[
                        {
                            value: 90,
                            name: '90%',
                            selected: true,
                            itemStyle: {
                                borderWidth: 0,
                                borderColor: '#1facff',
                            }
                        },
                        {value: 10, name: '10%'}
                    ]
                };

                this.chartOptions[3].option.title.text = '大数据分析覆盖率';
                this.chartOptions[3].option.color = ['rgb(255, 221, 26)', 'rgb(255, 240, 153)'];
                this.chartOptions[3].option.series[0] = {
                    name: '访问来源',
                    type: 'pie',
                    selectedOffset: 0,
                    selectedMode: 'single',
                    radius: ['55%', '70%'],
                    center: ['50%', '60%'],
                    label: {
                        normal: {
                            show: true,
                            position: 'center',
                            formatter: (params) => {
                                return (params.dataIndex === 0 ? '{num|'+ params.name + '}' : '');
                            },
                            rich: {
                                num: {
                                    fontSize: 23,
                                    fontWeight: 600,
                                    color: 'rgb(255, 221, 26)',
                                },
                                pre: {
                                    fontSize: 12,
                                    color: 'rgb(255, 221, 26)',
                                }
                            },
                            color: 'rgb(255, 221, 26)',
                            fontWeight: 600,
                            fontSize: 20
                        },
                    },
                    itemStyle: {

                    },
                    labelLine: {
                        normal: {
                            show: false
                        }
                    },
                    data:[
                        {value: 90, name: '90%', selected: true},
                        {value: 10, name: '10%'}
                    ]
                };

                this.chartOptions[4].option.title.text = '远程医疗覆盖率';
                this.chartOptions[4].option.color = ['rgb(0, 165, 255)', 'rgb(173, 226, 255)'];
                this.chartOptions[4].option.series[0] = {
                    name: '访问来源',
                    type: 'pie',
                    selectedOffset: 0,
                    selectedMode: 'single',
                    radius: ['55%', '70%'],
                    center: ['50%', '60%'],
                    label: {
                        normal: {
                            show: true,
                            position: 'center',
                            formatter: (params) => {
                                return (params.dataIndex === 0 ? '{num|'+ params.name + '}' : '');
                            },
                            rich: {
                                num: {
                                    fontSize: 23,
                                    fontWeight: 600,
                                    color: '#1facff',
                                },
                                pre: {
                                    fontSize: 12,
                                    color: '#1facff',
                                }
                            },
                            color: '#1facff',
                            fontWeight: 600,
                            fontSize: 20
                        },
                    },
                    itemStyle: {

                    },
                    labelLine: {
                        normal: {
                            show: false
                        }
                    },
                    data:[
                        {value: 95, name: '95%', selected: true},
                        {value: 5, name: '5%'}
                    ]
                };

                this.chartOptions[5].option.title.text = '设备覆盖率';
                this.chartOptions[5].option.color = ['rgb(0, 255, 217)', 'rgb(179, 255, 244)'];
                this.chartOptions[5].option.series[0] = {
                    name: '访问来源',
                    type: 'pie',
                    selectedOffset: 0,
                    selectedMode: 'single',
                    radius: ['55%', '70%'],
                    center: ['50%', '60%'],
                    label: {
                        normal: {
                            show: true,
                            position: 'center',
                            formatter: (params) => {
                                return (params.dataIndex === 0 ? '{num|'+ params.name + '}' : '');
                            },
                            rich: {
                                num: {
                                    fontSize: 23,
                                    fontWeight: 600,
                                    color: 'rgb(0, 255, 217)',
                                },
                                pre: {
                                    fontSize: 12,
                                    color: 'rgb(0, 255, 217)',
                                }
                            },
                            color: 'rgb(0, 255, 217)',
                            fontWeight: 600,
                            fontSize: 20
                        },
                    },
                    itemStyle: {

                    },
                    labelLine: {
                        normal: {
                            show: false
                        }
                    },
                    data:[
                        {value: 92, name: '92%', selected: true},
                        {value: 8, name: '8%'}
                    ]
                };

                this.chartOptions[6].option.title.text = '推送覆盖率';
                this.chartOptions[6].option.color = ['rgb(129, 61, 255)', 'rgb(211, 189, 255)'];
                this.chartOptions[6].option.series[0] = {
                    name: '访问来源',
                    type: 'pie',
                    selectedOffset: 0,
                    selectedMode: 'single',
                    radius: ['55%', '70%'],
                    center: ['50%', '60%'],
                    label: {
                        normal: {
                            show: true,
                            position: 'center',
                            formatter: (params) => {
                                return (params.dataIndex === 0 ? '{num|'+ params.name + '}' : '');
                            },
                            rich: {
                                num: {
                                    fontSize: 23,
                                    fontWeight: 600,
                                    color: 'rgb(129, 61, 255)',
                                },
                                pre: {
                                    fontSize: 12,
                                    color: 'rgb(129, 61, 255)',
                                }
                            },
                            color: 'rgb(129, 61, 255)',
                            fontWeight: 600,
                            fontSize: 20
                        },
                    },
                    itemStyle: {

                    },
                    labelLine: {
                        normal: {
                            show: false
                        }
                    },
                    data:[
                        {value: 100, name: '100%', selected: true},
                        {value: 0, name: '0%'}
                    ]
                };

                this.chartOptions[7].option.title.text = '大数据分析覆盖率';
                this.chartOptions[7].option.color = ['rgb(255, 221, 26)', 'rgb(255, 240, 153)'];
                this.chartOptions[7].option.series[0] = {
                    name: '访问来源',
                    type: 'pie',
                    selectedOffset: 3,
                    selectedMode: 'single',
                    radius: ['55%', '70%'],
                    center: ['50%', '60%'],
                    label: {
                        normal: {
                            show: true,
                            position: 'center',
                            formatter: (params) => {
                                return (params.dataIndex === 0 ? '{num|'+ params.name + '}' : '');
                            },
                            rich: {
                                num: {
                                    fontSize: 23,
                                    fontWeight: 600,
                                    color: 'rgb(255, 221, 26)',
                                },
                                pre: {
                                    fontSize: 12,
                                    color: 'rgb(255, 221, 26)',
                                }
                            },
                            color: 'rgb(255, 221, 26)',
                            fontWeight: 600,
                            fontSize: 20
                        },
                    },
                    itemStyle: {

                    },
                    labelLine: {
                        normal: {
                            show: false
                        }
                    },
                    data:[
                        {value: 100, name: '100%', selected: true},
                        {value: 0, name: '0%'}
                    ]
                };

                this.chartOptionBottom.option = Object.assign({}, JSON.parse(JSON.stringify(chartOption)));
                this.chartOptionBottom.option.title.text = '经开区医疗扶贫政策12个月帮扶人数';
                this.chartOptionBottom.option.xAxis[0].data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
                this.chartOptionBottom.option.xAxis[0].boundaryGap = ['20%', '20%'];
                this.chartOptionBottom.option.series[0].type = 'bar';
                this.chartOptionBottom.option.series[0].itemStyle = {
                    normal: {
                        color: function (params) {
                            let color;
                            if(params.data > 15){
                                color = '#22ac38'
                            }
                            if(params.data > 10 && params.data < 15){
                                color = '#f7e748'
                            }
                            if(params.data > 5 && params.data < 10){
                                color = '#ff8c00'
                            }
                            if(params.data < 5){
                                color = '#ff0000'
                            }

                            return color;
                        }
                    }
                };
                this.chartOptionBottom.option.series[0].barWidth = 20;
                this.chartOptionBottom.option.series[0].data = [1, 0, 1, 0, 5, 3, 7, 2, 3, 5, 4, 8];
            },

            // 初始化resize
            initResize(){
                window.addEventListener('resize', this.changeWindow, false);
            },

            // 移除resize
            removeResize(){
                window.removeEventListener('resize', this.changeWindow);
            },

            // window改变告诉子组件要重新渲染eChart
            changeWindow(){
                if(this.timer){
                    clearTimeout(this.timer);
                }

                this.timer = setTimeout(() => {
                    this.chartOptionBottom.resize = true;
                    for(let i = 0; i < 8; i++){
                        this.$set(this.chartOptions[i], 'resize', true);
                    }

                    setTimeout(() => {
                        this.chartOptionBottom.resize = false;
                        for(let i = 0; i < 8; i++){
                            this.$set(this.chartOptions[i], 'resize', false);
                        }
                    }, 10);
                }, 100)
            }
        }
    }
</script>

<style lang="scss">
    @import "../../assets/css/views/dashboard/dashboard";
</style>

总结


在使用标记状态时,使用num++感觉比使用resize状态容易些,但是状态不明显,不够语义化,所以还是使用状态,然后100ms之后改回来。

防抖处理之后,会有明显的延迟卡顿,没有立即改变效果好。可以定时器的时间改成10ms。

 

附上我的微信公众号,喜欢的可以关注一下

发布了5 篇原创文章 · 获赞 0 · 访问量 119

猜你喜欢

转载自blog.csdn.net/forteenBrother/article/details/105625306