Design and implementation of folding panel component

Design and implementation of folding panel component

Introduction

NutUI should be familiar to everyone, and front-end developers must have some knowledge of it. NutUI is a JD-style mobile component library that uses the Vue language to write applications that can be used on H5 and mini-program platforms.

Currently, NutUI has more than 70 components, supports on-demand reference, TypeScript, customized themes and other functions, and of course supports the latest Vue3 syntax, which can effectively help R&D personnel improve efficiency and improve development experience.

Now, let's get back to the topic. Today, let's learn about the implementation and design of the Collapse panel in NutUI, as well as the new knowledge points learned during the development process.

Folding panel design

In fact, whether in PC or M, the folding panel component is a common component. As the name suggests, it is a content area that can be folded/expanded. It is also widely used in scenarios such as navigation, text details, filtering and classification, etc.

During the component development phase, we usually conduct comparative analysis to learn from each other’s strengths and overcome their weaknesses. So we simply start the component development by comparing the functions.

The essence of components is to improve development efficiency. We achieve business needs by deconstructing and combining business scenarios. It is like a component library is a toolbox, and each component is a wrench, pliers and other tools in the box, providing various tools for business scenarios. How to create a suitable tool for work requires us to understand and think about daily business development.

Let’s explore together~

Expand and collapse

Now that the basic interaction of components is clear, the layout of our title and content is relatively simple. Now we need to complete the development of the interaction, that is, the expansion and folding functions.

It is actually very simple to implement the functions of expanding and collapsing. You just need to control the display and hiding of the content through a variable. Without considering other factors, this method is indeed the most efficient way.

 <template>
<div>
<div @click="handle">
Title
</div>
<div v-show="show">
Test content Test content Test content Test content Test content Test content Test content
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const show = ref(false);
const handle = () => {
show.value = !show.value;
}
</script>

However, this method may not be very friendly to our later functional expansion and interactive effects. So my solution is to change the height of the folded content, which is also easy to understand.

We mainly deal with the content. For this style, we set its height to 0 by default, which means the content is folded. Because each folded content is uncertain, we need to dynamically calculate the height after the content is filled. This method can also be regarded as an adaptation solution.

The purpose of my dynamic calculation is to achieve the animation effect later and improve the user experience. I use the height + transform method to achieve it, and use the CSS property will-change to optimize the animation effect.

will-change provides a way for web developers to tell the browser what changes will happen to the element, so that the browser can make corresponding optimization preparations in advance before the element attributes actually change. This optimization can prepare some complex calculations in advance, making the page more responsive.

 // Component core code
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;
}

The above code obtains the DOM of the element to calculate the content height offsetHeight and assigns a value, and realizes the collapse and expand animation effect through the change of height combined with transform.

Flexible title bar

The second is to improve the title bar function, add icons and custom positions and related animation functions. Let's first look at the right icon of basic usage. It corresponds to the expansion and collapse of the content. The interaction is an up arrow when expanding and a down arrow when collapsing. Then we can easily solve it by using an arrow icon based on the state of expansion or not. The solution is to use the rotate attribute of CSS3 and reverse it 180°.

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

In order to provide users with higher customization and better expansion component capabilities, the icon configuration API is exposed, such as custom icons, icon rotation angle, etc. These configurations refer to different scenarios, such as folding and rotating the content of some news reports by 90°.

Of course, the title bar text can also be configured with related icons, including the icon's position, color, size, etc. This function increases the user's personalized configuration, and it can be used to display certain important messages, new message reminders, unchecked information, and other scenarios.

Some component library developers may not have this configuration. First of all, I personally feel that it has nothing to do with the component. The design of the component needs to be connected with the business and some functions are abstracted, so that the function of the component can be better improved, including the expansion of the later components, which all grow in the development of the business.

Configuration item upgrade

During the later use process, we optimized and upgraded the component functions according to certain scenarios.

First, the subtitle configuration is added, which can be easily set through sub-title (PS: You can see an example in the picture above).

The search classification function in the mall mobile terminal, such as the scene in the figure below, will have default content displayed outside, and the rest of the content will be folded or expanded after folding, so the slot:extraRender API is added to allow this part of the content to exist in the form of a slot, which is convenient for developers to define different display forms and adjust the style.

The implementation of the above functions is also relatively simple. Just add a slot tag in the code to receive the incoming content.

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

Since slots are mentioned here, I will elaborate on them [silly smile]. Regarding the display of the title and content mentioned above, the design considers that it can save developers time and effort and has more operability. Basically, input parameters are received in the form of slots (only limited to this component, related to content display). In this way, even if the backend or frontend processes data with HTML tags, it can be easily identified without unnecessary processing.

Since panels can be expanded and collapsed, there are also prohibited operations. I provide a simple property setting disabled to determine whether it is operable, and the implementation method is to set the style.

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

Development and design extra

01Using variables in Scss

This function must be familiar to everyone. To put it simply, it can control the CSS style through JS. Currently, Vue3 supports us to use variables in CSS and directly put in the code.

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


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


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

Isn’t it simple? In fact, similar writing methods have been supported by similar plug-ins before.

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

If you are interested in these plug-ins, you can try them. The editor has used styled-components and it is easy to get started. Before getting started, it is recommended that you understand the concept of CSS-in-JS.

02Component Development Adaptation

Want to become a NutUI contributor? If you also want to contribute your own components to NutUI, here are some key points for adapting the mini-program~

It is relatively easy to obtain DOM elements in H5 development, either through document or ref. However, this method cannot be used when adapting mini programs, and we need to obtain them using the method provided by 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);
});
});

The above method can be used to obtain node information, including width, height, x, y, etc. You can try to view the obtained information. Another point to note is that when setting the style of an element, it is best to use the style variable in the component to receive it, and do not assign it directly.

 // Change style in a similar way
const style = reactive({
color: 'red',
height: '100px',
});


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

03vue3 component communication

When developing components, because the nut-collapse nut-collapse-item parent and child components need to communicate, I used the provide/inject method, so I made a simple study of this communication method.

As for the ways of component communication, props, emit, attrs, etc., everyone must have known it clearly, so I won't show you the details. Now I will simply share with you the parameter passing form of provide/inject, which already existed in Vue2.

 //a.vue component
//Create a provider
import { defineComponent, provide } from 'vue';
export default defineComponent({
setup () {
const msg: string = 'Hello NutUI';
// provide out
provide('msg', msg);
}
})
//b.vue component
//Receive data
import { defineComponent, inject } from 'vue'
export default defineComponent({
setup () {
const msg: string = inject('msg') || '';
}
})

Through the above two examples, the operation is very simple, but one thing needs to be noted, provide is not responsive. If you want to make it responsive, you need to pass in responsive data.

The data provided by provide does not take into account the component hierarchy, that is, the component that initiates provide can serve as a dependency provider for all its subordinate components.

The implementation principle of provide and inject is mainly achieved by using prototypes and prototype chains.

In Vue3, the provide function adds a key/value pair to the provides object property on the current component instance. Another thing is that if the provides of the current component and the parent component are the same, a link is established between the provides object in the current component instance and the parent, i.e. the prototype.

 function provide(key, value) {
if (!currentInstance) {
if ((process.env.NODE_ENV !== 'production')) {
warn(`provide() can only be used inside setup().`);
}
}
else {
// Get the provides property of the current component instance
let provides = currentInstance.provides;
// Get the provides property of the current parent component
const parentProvides = currentInstance.parent && currentInstance.parent.provides;
if (parentProvides === provides) {
// Object.create() is a way for es6 to create objects, which can be understood as inheriting an object, and the added properties are under the prototype.
provides = currentInstance.provides = Object.create(parentProvides);
}
provides[key] = value;
}
}

I will not go into details about the implementation of inject. If you are interested, you can go to the source code for a deeper understanding.

From the following code, we can roughly understand that inject first obtains the instance object of the current component, and then determines whether it is a root component. If it is a root component, it returns to the provides of appContext, otherwise it returns the provides of the parent component. If the current key has a value in provides, it returns the value, otherwise it determines whether there is a default content. If the default content is a function, it is executed and the proxy object of the component instance is bound to the this of the function through the call method, otherwise it directly returns the default content.

 function inject(key, defaultValue, treatDefaultAsFactory = false) {
// If it is called by a functional component, take currentRenderingInstance
const instance = currentInstance || currentRenderingInstance;
if (instance) {
// If intance is located in the root directory, it returns to the provides of appContext, otherwise it returns to the provides of the parent component
const provides = instance.parent == null
? instance.vnode.appContext && instance.vnode.appContext.provides
: instance.parent.provides;
if (provides && key in provides) {
return provides[key];
}
// If the second parameter is greater than 1, the default value is used, the third parameter is true, and the second value is a function, the function is executed.
else if (arguments.length > 1) {
return treatDefaultAsFactory && isFunction(defaultValue)
? defaultValue.call(instance.proxy)
: defaultValue;
}
}
}

It can be roughly understood that when the provide API is called, the parent's provides is set to the properties on the prototype object of the current provides object. When inject obtains the property value in the provides object, it gives priority to obtaining the properties of the provides object itself. If it cannot be found itself, it will search in the previous object along the prototype chain.

Summarize

This article mainly introduces the design ideas and implementation principles of the folding panel component in NutUI, and shares some problems encountered in development. I hope it can help everyone in development.

If you encounter problems during development, you can raise an issue at any time, and the NutUI team will take it seriously and solve the problem. If you have good components, both business and general, you can submit a PR to the NutUI component library. Everyone is welcome to participate in the co-construction.

<<:  Inference is completed in less than 1ms on iPhone 12, Apple proposes MobileOne, an efficient mobile backbone network

>>:  iOS16 public beta is released, you can try it, but pay attention to some details

Recommend

Snow leopards, big cats active in the snowy mountains

In nature, there is a big cat with a long tail th...

How to increase website traffic? How to increase website traffic?

Increasing website traffic is the dream of every ...

How scary! Hackers can steal your data even if you disconnect the network cable

Once a computer is infected with a virus or Troja...

Summary of highlights of Apple's press conference: These things are worth seeing

After watching the press conference with so much ...

10 common misunderstandings about operations!

In writing this article, I would like to clarify ...

[Optimization Tips] Headline Information Flow: Do you know all these tips?

When I get old, my hair turns white. Information ...

These common flowers are best trampled to death when you see them

In recent years, Canada goldenrod has suddenly be...

How to spend the middle age of programmers

I have read a lot of articles about the midlife c...

Lao Huqun's Getting Started Tutorial on Buying from 0 to 1

Taoke Park Lao Hu and major team leaders teach yo...

Who “painted” those colors in the fireworks?

When the colorful fireworks bloom in the dark nig...

The new Kia K5 has a younger look: the name "Diaosi Sanbao" is not in vain

It is said that the three treasures of losers are...