样式穿透和实现固钉效果

样式穿透和实现固钉效果样式穿透和实现固钉效果最近在写代码时遇到一个问题,就是我想要实现一个固钉效果,让某个元素在超过屏幕时会悬浮在屏幕上的某个位置

样式穿透和实现固钉效果

最近在写代码时遇到一个问题,就是我想要实现一个固钉效果,让某个元素在超过屏幕时会悬浮在屏幕上的某个位置。这就类似与flex布局。通常情况下许多的ui组件库都有提供affix组件来实现固钉效果,就比如element plus给我们提供的el-affix组件就有这样的效果,但是在某些情况之下el-affix就会失效。

在我们通常的vue项目中为了防止我的些的css样式冲突,我们都会在style上加上scoped

<style lang="scss" scoped>
<style>

关于scoped

就像这样,这时可能就会出现el-affix组件失效,其原因就是因为我加上了scoped这个属性,因为vu是单页面应用,所以我们写的不同页面最后都会被打包成一个..html文件,所以你在不同页面定义的css样式最后都会被引入到这个html文件中,这就导致如果你在不同的文件中定义的相同的文件名称,就很有可能导致样式错乱。所以就有了scoped,他的原理就是通过给样式加上识别标签来进行分类,不同的页面识别标签也就不同,
dd

如图div后面的一大串就是vue为我们加上的识别标签。
请添加图片描述

同样在引入的css文件中vue也会给我们加上识别标签,这样就不会出现样式名冲突的问题,但是我们有时在使用第三方样式库(比如element,ant等)时就会出现无法修改样式的问题,这是因为vue没有将识别标签加到组件上
请添加图片描述

vue只会在你的最外层上加上data-v-xxxxxx,但是在css文件中vue会帮你吧data-v-xxxxxx拼接在样式的最后一段。所以就导致了你都样式永远无法命中我们想要的元素,除非我们把scoped属性去掉,但是这样就可能倒是我们上面说的问题,所以我们不推荐。

样式穿透

这时我们就引入了另一个概念,就是样式穿透。样式穿透的使用方式很多,有>>>::v-deep/deep/等。在我们使用了样式穿透后vue就会把唯一的识别标志移到我们写::v-deep之后

.home_main {
  max-width: 1100px;
  margin: 48px auto 28px;
  display: flex;
  .page{
    &:deep(button){
      border-radius: 4px;
      background-color: #fff;
      box-shadow: 0 2px 1px -2px rgb(0 0 0 / 20%), 0 2px 2px 0 rgb(0 0 0 / 14%), 0 1px 5px 0 rgb(0 0 0 / 12%);
    }
    &:deep(.el-pager){
     .number{
       border-radius: 4px;
       background-color: #fff;
       box-shadow: 0 2px 1px -2px rgb(0 0 0 / 20%), 0 2px 2px 0 rgb(0 0 0 / 14%), 0 1px 5px 0 rgb(0 0 0 / 12%);
     }
     .more{
       border-radius: 4px;
       box-shadow: 0 2px 1px -2px rgb(0 0 0 / 20%), 0 2px 2px 0 rgb(0 0 0 / 14%), 0 1px 5px 0 rgb(0 0 0 / 12%);
       background-color: #fff;
     }
     .active{
       box-shadow: 0 2px 4px -1px rgb(0 0 0 / 20%), 0 4px 5px 0 rgb(0 0 0 / 14%), 0 1px 10px 0 rgb(0 0 0 / 12%);
     }
    }
  }

在这里插入图片描述

再看没有加样式穿透之前的样式
在这里插入图片描述

随意通过样式穿透我们就可以实现scoped所带来的副作用,顺便一提>>>的兼容性不是很高,所以不是很推荐,最好写后面两种。(因为最近在写vue3的项目,在vue3中提示使用&:deep(.name)来实现样式穿透,目前我也不知道他是vue3的特定写法还是,新的写法,就和大家分享一下。还有一个我觉得说样式穿透讲的挺好的视频https://www.douyin.com/video/6987146287137180966 如果还是有不懂什么是样式穿透以及原理的,推荐可以看看)

.el-card {
  &:deep(.el-card__body) {
    padding: 0;
    display: flex;
    height: 250px;
    align-items: center;
  }
}

说到这里大部分的样式问题都可以解决,但是当我去尝试el-effix时结果发现还是不行,弄得我快疯了,最后去看了el-affix的源码才知道,elememt plus 是通过判断el-affix指定的容器,当超过屏幕时给内层div添加样式,让el-affix变成悬浮的状态,在没超过屏幕的状态下移除div的悬浮样式
AAA
在这里插入图片描述

样式穿透可以解决当容器超出屏幕悬浮的问题,但是当回到原始位置时,就无法消除内层div的el-affix--fixed样式,导致出现一直悬浮的状态,可能是我技术还不够吧,如果有解决的朋友可以分享下。

我只好另找出路,既然element plus的组件用不了,那就自己写一个(其实是网上cv的),由于我的项目的vue3+ts的,但是网上大多都是vue2和js的所以就自己改了那么一点点,本人小白,写的不好的地方还请轻点喷,不多说,上代码

<template>
  <div class="affix-placeholder" :style="data.wrapStyle">
    <div class="affix-div" :class="{'affix': data.affixed}" :style="data.styles">
      <slot />
    </div>
  </div>
</template>

<script lang="ts">
import {
  computed,
  defineComponent,
  getCurrentInstance,
  onMounted,
  reactive,
  onUnmounted } from 'vue';
import { Styles, affixProps } from './affixProps';

export default defineComponent({
  name: 'Affix',
  props: affixProps,

  setup(props: any) {
    const data = reactive({
      affixed: false,
      styles: {} as Styles,
      affixedClientHeight: 0,
      wrapStyle: {},
    });
    const instance = getCurrentInstance();
    const getScroll = function(w: Window, top?: boolean){
      let ret = w[`page${(top ? 'Y' : 'X')}Offset`];
      const method = `scroll${top ? 'Top' : 'Left'}`;
      if (typeof ret !== 'number') {
        const d = w.document;
        // ie6,7,8 standard mode
        ret = d.documentElement[method];
        if (typeof ret !== 'number') {
          // quirks mode
          ret = d.body[method];
        }
      }
      return ret;
    };
    const getOffset = function (element: Element) {
      const rect = element.getBoundingClientRect();
      const body = document.body;
      const clientTop = element.clientTop || body.clientTop || 0;
      const clientLeft = element.clientLeft || body.clientLeft || 0;
      const scrollTop = getScroll(window, true);
      const scrollLeft = getScroll(window);
      return {
        top: rect.bottom + scrollTop - clientTop - data.affixedClientHeight,
        left: rect.left + scrollLeft - clientLeft
      };
    };
    const offsets = computed(()=>{
      if (props.boundary) {
        return 0;
      }
      return props.offset;
    });
    const handleScroll = () => {
      const scrollTop = getScroll(window, true) + offsets.value; // handle setting offset
      const elementOffset = getOffset(instance?.proxy?.$el);
      if (!data.affixed && scrollTop > elementOffset.top) {
        data.affixed = true;
        data.styles = {
          top: `${offsets.value}px`,
          left: `${elementOffset.left}px`,
          width: `${instance?.proxy?.$el.offsetWidth}px`
        };
        props.onAffix(data.affixed);
      }
      // if setting boundary
      if (props.boundary && scrollTop > elementOffset.top) {
        const el = document.getElementById(props.boundary);
        if (el) {
          const boundaryOffset = getOffset(el);
          if ((scrollTop + offsets.value) > boundaryOffset.top) {
            const top = scrollTop - boundaryOffset.top;
            data.styles.top = `-${top}px`;
          }
        }
      }
      if (data.affixed && scrollTop < elementOffset.top) {
        data.affixed = false;
        data.styles = {};
        props.onAffix(data.affixed);
      }
      if (data.affixed && props.boundary) {
        const el = document.getElementById(props.boundary.slice(1));
        if (el) {
          const boundaryOffset = getOffset(el);
          if ((scrollTop + offsets.value) <= boundaryOffset.top) {
            data.styles.top = 0;
          }
        }
      }
    };
    onMounted(()=> {
      data.affixedClientHeight = instance?.proxy?.$el.children[0].clientHeight;
      data.wrapStyle = { height: `${data.affixedClientHeight}px` };
      window.addEventListener('scroll', handleScroll);
      window.addEventListener('resize', handleScroll);
    });
    onUnmounted(()=>{
      window.removeEventListener('scroll', handleScroll);
      window.removeEventListener('resize', handleScroll);
    });
    return {
      data,
    };
  }
});
</script>

<style lang="sass">
.affix
  position: fixed
</style>

import { PropType } from 'vue';
import { FunctionType } from '@/constant/Type';


export interface Styles{
  top?: string | number,
  left?: string | number,
  width?: string | number,
}

export const affixProps = {
  offset: {
    type: Number as PropType<number>,
    default: 0,
  },
  onAffix: {
    type: Function as PropType<FunctionType>,
    default() {}
  },
  boundary: {
    type: String as PropType<string>,
    default: 0,
  }
};

export interface FunctionType {
  (): void;
}

这样一个affix组件就写完了,但是经过我的一轮测试,发现还是不行,最后发现是因为自定义的affix组件是在初始化时获取父级组件或绑定的父级组件的高度,但是我的父级组件的高度是动态的,通过后端获取数据后再通过v-for渲染,所以初始高度很小,在高度改变后的affix内的父级高度却没有发生变化。

sticky粘性布局

在经过了又一番的查找资料后,我又看到了一个解决方案,就是posistion:sticky在css的posistion中我们常用到的属性有relativeabsolute,sticky就在粘性布局我们可以通过设置

position: -webkit-sticky;
position: sticky;

这个属性来实现类似固钉的效果,在官方文档中的说明是sticky 英文字面意思是粘,粘贴,所以可以把它称之为粘性定位。

position: sticky; 基于用户的滚动位置来定位。

粘性定位的元素是依赖于用户的滚动,在 position:relative 与 position:fixed 定位之间切换。

它的行为就像 position:relative; 而当页面滚动超出目标区域时,它的表现就像 position:fixed;,它会固定在目标位置。

元素定位表现为在跨越特定阈值前为相对定位,之后为固定定位。

这个特定阈值指的是 top, right, bottom 或 left 之一,换言之,指定 top, right, bottom 或 left 四个阈值其中之一,才可使粘性定位生效。否则其行为与相对定位相同。

反正看这么多也不是很懂先cv再说,不出意外的话就要出意外了,果然不行,没办法只能再去看文档了,最后才知道posistion:stucky要生效也需要有条件,总结来说

  1. 须指定 top, right, bottom 或 left 四个阈值其中之一,就是需要设置这几个元素任意一个值,并且 top 和 bottom 同时设置时,top 生效的优先级高,left 和 right 同时设置时,left 的优先级高。
  2. 最重要的一点,在设置了posistion:stucky后该元素的所有父级元素都不能设置overflow:hidden不然也不会生效,具体可以查看官方文档。

最后终于可以了,就这一个问题困了我三天,本以为事情就这样结束了,结果过几天天我在加其他的功能时突然发现,又失灵了。。。。。

最后排查了好久才发现原来是原先在设置el-scrollbaroverflow时没有设置成功,再加一个

overflow: visible !important;

终于所有问题都解决了。

今天的文章样式穿透和实现固钉效果分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:http://bianchenghao.cn/63130.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注