perfect-scrollbar vue3自定义指令(解决内容高度变化自动刷新滚动条及指令参数传递)

1.perfect-scrollbar封装自定义指令(并优化处理遇到的问题)

2.页面正文最外层容器使用滚动条后,页面子元素使用自定义滚动条会出现在子元素鼠标在子元素上滚动时有时需要自动传递到外层的滚动条,有时需要禁止向祖先级别传递。可以通过自定义指令传参数wheelPropagation来控制

3.解决滚动区域内容变化时滚动条无法更新

4.如果需要修改替换ant-design-vue或者element-ui的textarea滚动条,可以用div包裹textarea,textarea设置为autosize,但是同样会出现textarea变大后滚动条大小位置不会变化问题,又因为addResizeListener无法监听textarea的变化,所以可以给textarea外层再加一层div,具体用法在最后

封装自定义指令 

创建/src/utils/event/index.ts,使用到yarn add resize-observer-polyfill插件来监听元素尺寸变化,类似weindow.addEventListener,很有用的一个插件

import ResizeObserver from 'resize-observer-polyfill';

const isServer = typeof window === 'undefined';

/* istanbul ignore next */
function resizeHandler(entries: any[]) {
  for (const entry of entries) {
    const listeners = entry.target.__resizeListeners__ || [];
    if (listeners.length) {
      listeners.forEach((fn: () => any) => {
        fn();
      });
    }
  }
}

/* istanbul ignore next */
export function addResizeListener(element: any, fn: () => any) {
  if (isServer) return;
  if (!element.__resizeListeners__) {
    element.__resizeListeners__ = [];
    element.__ro__ = new ResizeObserver(resizeHandler);
    element.__ro__.observe(element);
  }
  element.__resizeListeners__.push(fn);
}

/* istanbul ignore next */
export function removeResizeListener(element: any, fn: () => any) {
  if (!element || !element.__resizeListeners__) return;
  element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
  if (!element.__resizeListeners__.length) {
    element.__ro__.disconnect();
  }
}

export function triggerWindowResize() {
  const event = document.createEvent('HTMLEvents');
  event.initEvent('resize', true, true);
  (event as any).eventType = 'message';
  window.dispatchEvent(event);
}

创建src/directives/scrollbarDirective.ts,添加下面代码

import type { Directive, App } from 'vue';
import usePerfectScrollbar from '/@/hooks/web/usePerfectScrollbar'
const { install, uninstall } = usePerfectScrollbar()
const scrollbarDirective: Directive = {
    mounted(el,binding) {
        install(el,binding)
    },
    beforeUnmount(el) {
        uninstall(el)
    }
}

export function setupScrollbarDirective(app: App) {
    app.directive('scrollbar', scrollbarDirective);
}

export default scrollbarDirective;

创建src/hooks/web/usePerfectScrollbar.ts,添加如下代码

import PerfectScrollbar from 'perfect-scrollbar'
import 'perfect-scrollbar/css/perfect-scrollbar.css'
import {addResizeListener,removeResizeListener} from '/@/utils/event/index'

export default function usePerfectScrollbar() {
    //这里原来网上的方案是使用ps变量来存储new PerfectScrollbar实例,实际使用中发现嵌套使用滚动条或者页面同时多个地方使用滚动条实例化结果会被相互覆盖,这样到时你需要使用ps去调用update或者destory时会找不到这边实例了,于是在el上定义一个_ps_来存储每个容器的实例
    const resize = (el: any) => {
        if(el && el?._ps_ && el._ps_ instanceof PerfectScrollbar){
            el._ps_.update()
        }
    }
    return {
        install(el: any, binding: any = null) {
            if(el){
                if(el?._ps_ && el._ps_ instanceof PerfectScrollbar){
                    el._ps_.update(); // 此时 el._ps_  已经存在update()方法 原型链查找
                }else{
                    let options = {
                        wheelSpeed: 2,
                        wheelPropagation: false
                    }
                    if(binding.value){
                        options = {...options,...binding.value}
                    }
                    el._ps_ = new PerfectScrollbar(el, options)
                    try{
                        //使用自定义指令的时候最好用div将内容包裹一下例如
                        //<div v-scrollbar>
                        //    <div>//这层div就是额外加的,目的是内部内容变化可以被          addResizeListener监听到并刷新滚动条,否则你的内容高度变化了滚动条不会动
                        //        其它内容
                        //    </div>
                        //</div>
                        //这里之所以多了个auto-size-wrapper就是因为我之前做的时候页面容器布局里面是多个平级的div一个header一个footer,最后是content,导致获取滚动容器下第一个div时不是想要的监听的div所以在content上加了个class,这里优先查询这个class
                        // @ts-ignore
                        const firstDiv = el.querySelector('.auto-size-wrapper') || el.querySelector('div');
                        if(firstDiv){
                            addResizeListener(firstDiv,() => resize (el))
                        }
                    }catch(error){
                    }
                }
            }
        },
        uninstall(el: any) {
            try{
                // @ts-ignore
                const firstDiv = el.querySelector('.auto-size-wrapper') || el.querySelector('div');
                if(firstDiv){
                    removeResizeListener(firstDiv,() => resize(el))
                }
            }catch(error){
            }
            if(el && el?._ps_ && el._ps_ instanceof PerfectScrollbar){
                el._ps_.destroy()
                el._ps_ = null
            }
        }
    }
}

在main.ts或者你项目初始化合适的地方调用setupScrollbarDirective注册指令

如果需要改变perfect-scrollbar的hover效果,可以在app.vue添加样式,我这里隐藏掉hover效果,因为hover后会变宽

.ps__rail-y:hover,
.ps__rail-x:hover {
  /* 去除hover时的默认样式 */
  background-color: transparent!important;
}
.ps__rail-x:hover > .ps__thumb-x, .ps__rail-x:focus > .ps__thumb-x, .ps__rail-x.ps--clicking .ps__thumb-x{
  height: 6px!important;
}
.ps__rail-y:hover > .ps__thumb-y, .ps__rail-y:focus > .ps__thumb-y, .ps__rail-y.ps--clicking .ps__thumb-y{
  width: 6px!important;
}
.ps .ps__rail-x:hover, .ps .ps__rail-y:hover, .ps .ps__rail-x:focus, .ps .ps__rail-y:focus, .ps .ps__rail-x.ps--clicking, .ps .ps__rail-y.ps--clicking{
  /* 去除hover时的默认样式 */
  background-color: transparent!important;
}

如果发下鼠标在使用了v-scrollbar的子元素上滚动时祖先层的perfect-scrollbar无法滚动可以给v-scrollbar传递参数允许鼠标滚动向外层传递。v-scrollbar="{wheelPropagation: true}",灵活运用,有时可能需要在子元素滚动时禁止外层滚动,例如自定义select的下拉有很多选项,你滚动的时候不希望页面滚动。注意:这个只对v-scrollbar的滚动条生效,如果外层是使用的原生滚动条,这个用法就没有效果

<div class="item-box" v-scrollbar="{wheelPropagation: true}">
                  <div class="item" v-for="(item,index) in productList" :key="index" @click="goStashDetail(item)">
                      <div class="image-box">
                          <Icon class="placeholder-icon" icon="ImageHolder|svg" :size="32" />
                          <img v-lazyload="item.coverPathDefaultUrl">
                      </div>
                      <div>
                          <div class="title product-name ellipsis">{{item.name}}</div>
                          <div class="subtitle">{{item.itemType=='908627439196573500'?'Merch':item.itemType=='908627439196573600'?'Digital':item.itemType=='908627439196573700'?'Event':''}}</div>
                      </div>
                  </div>
                </div>

已ant-design-vue为例,textarea滚动条应用如下,这是我代码中的一个片段对textarea处理部分

<div 
    class="custom-textarea"
    v-scrollbar
    >
      <div>
        <Textarea 
        v-model:value="fieldValue"
        class="form-textarea" 
        :class="{'input-allow-clear': clearable,'input-show-prefix': prefix!==''}" 
        :placeholder="placeholder"
        :disabled="disabled"
        :autoSize="{minRows: 2}"
        @focus="inputFocus"
        @blur="inputBlur"
        @keyup="inputKeyup"
        @change="inputChange"
        />
      </div>
    </div>

附加聚焦样式

.custom-textarea{
  height: 78px;
  padding: 16px;
  background-color: #090a0a;
  color: #F7F9FA;
  border: 1px solid #343434;
  border-radius: 8px;
  padding: 16px;
  font-size: 14px;
  padding-right: 40px;
}
.custom-textarea:focus-within{
  border-color: #F68FA6!important;
  box-shadow: 0 0 0 2px rgba(246, 143, 166, 0.2)!important;
  outline: 0!important;
}

End...

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值