折叠面板组件的设计与实现

发表于 2年以前  | 总阅读数:281 次

前言

NutUI,大家应该不陌生吧,前端开发的同学肯定是有些了解的。NutUI 是一个京东风格的移动端组件库,使用 Vue 语言来编写可以在 H5,小程序平台上的应用。

目前 NutUI 拥有 70+ 组件,支持按需引用,支持 TypeScript,支持定制主题等功能,当然也支持最新的 Vue3 语法,在开发上能有效帮助研发人员提升效率,改善开发体验。

言归正传,今天我们一起了解 NutUI 中折叠面板 Collapse 的实现与设计,以及在开发过程中学习到的新知识点。

折叠面板设计

其实折叠面板组件无论是在 PC 还是 M ,都是比较常见的组件,顾名思义就是可以折叠/展开的内容区域。使用场景也比较广泛,例如导航、文字类详情、筛选分类等;

在组件开发阶段,我们通常都会进行对比分析,取长补短。所以我们简单通过功能上的对比来入组件的开发。

组件的本质就是提升开发效率的,我们通过对业务场景的解构和组合配置方式实现业务需求。好比组件库是一个工具箱,每个组件就是箱子里的扳手、钳子等工具,为业务场景提供各种工具,如何去打造一个合适趁手的工具干活,就需要我们对平时的业务开发有所了解和思考。

让我们一起来探索吧~

实现展开收起

组件的基本交互已经明了,那我们的标题和内容的布局方式就比较简单了。现在我们需要去完成交互的开发,也就是展开折叠的功能。

实现展开折叠的功能其实很简单,就是通过一个变量控制内容的展示隐藏就可以了,不用考虑其他因素的情况下,这种方法的确是最高效的方式。


<template>
  <div>
    <div @click="handle">
      标题
    </div>
    <div v-show="show">
      测试内容测试内容测试内容测试内容测试内容测试内容
    </div>
  </div>
</template>
<script setup>
    import { ref } from 'vue';
    const show = ref(false);
    const handle = () => {
      show.value = !show.value;
    }
</script>

但是采用这种方式可能对我们后期的功能扩展和交互效果不太友好。所以我的方案是通过改变折叠内容的 height 的方式实现的,当然实现这个方法也比较好理解。

我们主要处理 content 的内容,对于这块样式我们对它的 height 默认是 0,也就是内容是折起的状态。因为每个折叠内容是无法确定的,所以我们需要动态计算内容填充后的高度,这种方式也算是一种适配方案。

我动态计算的目的是为了实现后面动画效果,提升用户体验感。我利用的是 height + transform 的方式实现的,同时使用 css 的属性 will-change 对动画效果进行优化。

will-change 为 web 开发者提供了一种告知浏览器该元素会有哪些变化的方法,这样浏览器可以在元素属性真正发生变化之前提前做好对应的优化准备工作。这种优化可以将一部分复杂的计算工作提前准备好,使页面的反应更为快速灵敏。

// 组件部分核心代码
const wrapperRefEle: any = wrapperRef.value;
const contentRefEle: any = contentRef.value;
if (!wrapperRefEle || !contentRefEle) {
    return;
}
const offsetHeight = contentRefEle.offsetHeight || 'auto';
if (offsetHeight) {
    const contentHeight = `${offsetHeight}px`;
    wrapperRefEle.style.willChange = 'height';
    wrapperRefEle.style.height = !proxyData.openExpanded ? 0 : contentHeight;
}

以上代码就是通过获取元素的 DOM 来计算出内容的高度 offsetHeight 并赋值,通过高度的变化结合 transform 实现收起展开的动画效果。

灵活的标题栏

其次就是标题栏功能的完善,增加图标及自定义位置和相关动画功能。我们先来看下基本用法的右侧图标,它和内容的收起展开是相呼应的,交互上展开时是上箭头收起时是下箭头。那么我们根据是否展开的状态为变量,使用一个箭头图标就可以轻松搞定。实现的方案就是利用 css3 的 rotate 属性,反转 180° 就可以了。

if (parent.props.icon && !proxyData.openExpanded) {
  proxyData.iconStyle['transform'] = 'rotate(0deg)';
} else {
  proxyData.iconStyle['transform'] = 'rotate(' + parent.props.rotate + 'deg)';
}

为了用户的自定义性更高,更好的扩展组件能力,对外暴露了关于图标配置的 API,比如自定义图标、图标的旋转角度等。这些配置参考不同场景,比如某些新闻报道的内容折叠旋转 90° 。

当然,标题栏文字也可以配置相关图标,包括图标的位置、颜色、大小等。这种功能增加了用户的个性化配置,他可以用来展示某些重要消息、新消息提醒,未查看信息等场景使用。

某些组件库的开发者可能没有此配置,首先个人感觉和组件是无关的。组件的设计是需要与业务之间进行衔接,抽象出一些功能,这样能更好的完善组件的功能,包括后期组件的扩展等,都是在业务发展中成长的。

配置项升级

在后期的使用过程中,我们根据某些场景对组件功能进行了优化升级。

首先增加了副标题的配置,通过 sub-title 就可以轻松设置(PS: 上图??可看到示例)。

商城类移动端中的搜索分类功能,比如下图的这种场景。它会有默认的内容展示在外面,在折叠后其余内容进行折叠或展开,所以新增了 slot:extraRender API,让这部分内容以插槽的形式存在,方便开发者定义不同的展示形式,便于样式的调整等。

以上功能的实现也比较简单,就是在代码的中增加一个 slot 标签接收传入的内容即可。


<view v-if="$slots.extraRender">
    <div>
        <slot name="extraRender"></slot>
    </div>
</view>

在这里既然提到了 slot,我就多?嗦一下[憨笑]。关于上述提到的标题及内容的展示,设计的时候考虑能让开发者省时省力,有更多的可操作性,基本上都是以 slot 的形式来接收入参(仅限于本组件,内容展示相关),这样的话即使后端或者前端处理数据携带 HTMl 标签也可以轻松识别,无需多余处理。

面板既然都可以展开收起操作,那么反之也有禁止操作的。我提供了一个简单的属性设置 disabled 来确定是否可操作,实现方式就是通过设置 style 样式实现的。

.nut-collapse-item-disabled {
    color: #c8c9cc;
    cursor: not-allowed;
    pointer-events: none;
}

开发设计番外

01 Scss 中使用变量

这个功能大家想必也不陌生,说白了就是可以通过 JS 控制 CSS 的样式,目前 Vue3 支持我们使用在 CSS 中使用变量,直接上代码。


<template>
  <span>NutUI</sapn>
</template>

<script>
export default {
  data () {
    return {
      color: 'red'
    }
  }
}
</script>

<style vars="{ color }" scoped>
span {
  color: var(--color);
}
</style>

是不是很简单,其实类似的写法,在之前就有类似的插件支持的。

  • emotion
  • jss
  • styled-components
  • aphrodite
  • radium
  • glamor

这些插件大家感兴趣的可以尝试一下,小编用过 styled-components,还是很容易上手的,在上手前建议大家了解下 CSS-in-JS 的概念。

02 组件开发适配

想成为 NutUI 的 contributor 吗?如果也想为 NutUI 贡献自己的组件,下面可是适配小程序的一些要点哟~

在 H5 开发时获取 DOM 元素是比较容易的,通过 document 或者 ref 都可以。但是我们在适配小程序的时候这种方式是获取不到的,需要根据 Taro 提供的方法去获取。

import Taro, { eventCenter, getCurrentInstance as getCurrentInstanceTaro } from '@tarojs/taro';
eventCenter.once((getCurrentInstanceTaro() as any).router.onReady, () => {
  const query = Taro.createSelectorQuery();
  query.selectAll('.collapse-content').boundingClientRect();
  query.exec((res) => {
    console.log(res);
  });
});

通过以上方法可以获取到节点的信息,包括 width、height、x、y 等,大家可以体验试一下查看获取的信息。还有一点需要注意,就是在给元素设置 style 样式时,最好是在组件中使用 style 变量接收,不要直接赋值。


// 类似这种方式改变 style
const style = reactive({
    color: 'red',
    height: '100px',
});

const change = () => {
    style.color = 'blue';
}

03 vue3 组件通信

在组件开发时,因为 nut-collapse nut-collapse-item 父子组件需要进行通信,我使用的是 provide/inject 的方式,所以对此通信方式进行了简单的的学习了解。

关于组件通信的方式,props、emit、attrs 等等方式,大家必然已了然于胸,我就不献丑了。现在我简单和大家分享一下 provide/inject 的传参形式,这个 API 在 vue2 的时候已经存在。


//a.vue 组件
//创建一个 provide
import { defineComponent, provide } from 'vue';
export default defineComponent({
  setup () {
    const msg: string = 'Hello NutUI';
    // provide 出去
    provide('msg', msg);
  }
})
//b.vue 组件
//接收数据
import { defineComponent, inject } from 'vue'
export default defineComponent({
  setup () {
    const msg: string = inject('msg') || '';
  }
})

通过以上 2 个示例,操作是不是非常简单,但需要注意一点,provide 不是响应式的,如果你要使其具备响应性,你需要传入也应该是响应式数据。

provide 提供的数据不考虑组件层次结构,也就是发起 provide 的组件都可以作为其所有下级组件的依赖提供者。

provide 和 inject 的实现原理主要是利用了原型和原型链来实现。

在 Vue3 中 provide 函数就是给当前组件实例上的 provides 对象属性,添加键值对 key/value。还有一个地方就是如果当前组件和父级组件的 provides 相同时,在当前组件实例中的 provides 对象和父级,则建立链接,即原型 prototype。


function provide(key, value) {
    if (!currentInstance) {
        if ((process.env.NODE_ENV !== 'production')) {
            warn(`provide() can only be used inside setup().`);
        }
    }
    else {
        // 获取当前组件实例的 provides 属性
        let provides = currentInstance.provides;
        // 获取当前父级组件的 provides 属性
        const parentProvides = currentInstance.parent && currentInstance.parent.provides;
        if (parentProvides === provides) {
            // Object.create() es6创建对象的一种方式,可以理解为继承一个对象,添加的属性是在原型下。
            provides = currentInstance.provides = Object.create(parentProvides);
        }
        provides[key] = value;
    }
}

关于 inject 的实现我就不多赘述了,大家有兴趣的可以去根据源码做更深入的了解。

从下面代码可以大致了解,inject 先获取当前组件的实例对象,然后判断是否根组件,如果是根组件则返回到 appContext 的 provides,否则就返回父组件的 provides。如果当前的 key 在 provides 上有值,就返回该值,反之则判断是否存在默认内容,默认内容如果是个函数,就执行并且通过 call 方法把组件实例的代理对象绑定到该函数的 this 上,否则就直接返回默认内容。


function inject(key, defaultValue, treatDefaultAsFactory = false) {
    // 如果是被一个函数式组件调用则取 currentRenderingInstance
    const instance = currentInstance || currentRenderingInstance;
    if (instance) {
    // 如果intance位于根目录下,则返回到appContext的provides,否则就返回父组件的provides
        const provides = instance.parent == null
            ? instance.vnode.appContext && instance.vnode.appContext.provides
            : instance.parent.provides;
        if (provides && key in provides) {
            return provides[key];
        }
        // 如果参数大于1个 第二个则是默认值 ,第三个参数是 true,并且第二个值是函数则执行函数。
        else if (arguments.length > 1) {
            return treatDefaultAsFactory && isFunction(defaultValue)
                ? defaultValue.call(instance.proxy) 
                : defaultValue;
        }
    }
}

大致可以这么理解 provide API 调用的时候,设置父级的 provides 为当前 provides 对象原型对象上的属性,在 inject 获取 provides 对象中的属性值时,优先获取 provides 对象自身的属性,如果自身查找不到,则沿着原型链向上一个对象中去查找。

总结

本文主要介绍了 NutUI 中折叠面板组件的设计思路与实现原理,并分享了一些开发中遇到的问题,希望能在开发中帮到大家。

如果在开发中遇到问题,可随时提 issue,NutUI 团队的同学都会认真对待并解决问题。如您有好的组件,业务类、通用类的都可,都可向 NutUI 组件库提交 PR,非常欢迎大家参与共建。

最后,非常感谢一直以来支持 NutUI 的团队及同学,大家的需求与建议,让我们的组件库越来越好,我们也会不断努力,力争更上一层楼!

文章参考链接:

  1. Vue3 的 Provide / Inject 的实现原理:

https://juejin.cn/post/7064904368730374180

  1. 一文读懂vuex4源码,原来provide/inject就是妙用了原型链?:

https://juejin.cn/post/6963802316713492516

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/d-4WIcBeitmAAs7-9I18NA

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237227次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8063次阅读
 目录