Thinking of large H5 project development

Unconsciously, it is the fourth time that I have taken charge of the annual H5 event, which means that it has been 4 years. Alas, the time has gone so fast that I should make some summary.

Every year, there will be a large H5 project launched, the logic of these projects in general, they will not be very different, but each time will have different styles, conditions and gameplay. If every big activity is writing dead logic and cannot be reused, the next time the H5 project comes and writes it again, there is no need.

If you can make these components universal configurable, plug and play. Then it will certainly greatly improve the development efficiency, while ensuring the stability of the project. Just component code logic will be more complex, the development difficulty is high, it just need to consider the components within a button to the button color, size, the font style and the background color button, as well as the button is design change of state, if you have to consider this a logical or a state change is the possibility of some linkage. So, a generic component has a lot of logic to consider and implement.

For example, this year’s H5 has a “My Prizes” module (page or popup) that displays information about prizes, time, quantity, and other buttons at the bottom. Below are my “My prizes” list presentation modules from two different events.The general framework of the two styles of components is the same. Click on the sidebar “My Rewards” and “My backpack”, but the presentation form and display data type and button click events are different.

So, it doesn’t make much sense to write something like this the third H5, or the next H5. Therefore, modules that are used more frequently must be discussed with the business side. We can take such a module and define a basic interaction and prototype, and unify it into a general purposeThe list showsComponent, which must support general style presentation as well as special style presentation, such as the followingThe one on the far left is the normal list style, and the one on the right is some special card and text style, so there are a lot of things to consider for a component.

This is calledThe list showsThe reason component is not called “my Prize component” is that we only need parameters to control what it displays, what the title is, what the name of the button is, and what the logic is when it clicks, not just my prize list, but other data as well. At the same time, such generic components can be applied to a variety of H5s. Components are ready to use or for some H5 auto-generated platforms, as long as the parameters are passed according to the documentation.Data initializes the list of components, which is separated from business. This becomes a very pure list of components, which can display arbitrary data, as long as the parameter is passed according to the format. I’m just going to write a very simple DEMO, and I’ll talk about input arguments and function bindings later.

Planning common Components

After getting the design draft, identify common modules and then make a common rule based on the differences between similar modules. The following is an overview of the large H5 design draft:

Category 1: sectionals, qualifiers, pop-ups, rules and votes, etc

The second category: sprint, finals, PK modules and so on

The big H5 is divided into several stages, sectionals, qualification, sprint and grand Final. It seems to be a lot of content, so we need to find similar modules and communicate with the business side. From these stages, we can separate them into components

  1. TAB page cutting component
  2. Progress bar component
  3. List recording component
  4. Timeline component
  5. Leaderboard component
  6. Countdown module
  7. Vote components
  8. Sidebar suspension components
  9. Pk module components
  10. My information presentation component
  11. At the top of the component
  12. Suction bottom components
  13. Button component
  14. Anchor display component

Here just take a part of the components to describe the implementation of the idea, all written words are too much, and some places on the logical implementation is also more similar.

Common parameters for the component

Component incoming parameter

The configuration parameters required by a generic component are generally summarized into several types, the most important being all the values that the component needs to use, which is the initialization parameter. Second, some style configuration or global parameters of the component are used to assist, and some cases require a specific key belonging to the component. Of course, it is not to say that the style and global parameters are not important parameters, but according to the needs of the business, the possible style parameters are the key. This is also possible, and the specific nature of the business or the component itself is considered, but when making components, the priority is to realize the function. The following is a custom THAT I use to encapsulate generic components: Data, styleForm, commonStyle, Global, And componentKey. Here is an example of component binding parameters and methods:

<template> <head-section :data="headData" :global="global" :styleForm="headConfig" :commonStyle="headCommonStyle" :componentKey="headComponentKey" @methods="headMethods" /> </template> <script> export default { data(){ return { // HeadData: {}, headConfig: {}, headCommonStyle: {}, headComponentKey: {},}} </script>Copy the code

Data component initialization parameters

Data is the initialization parameter passed to the component or all of the render component’s data of type Object. Components can fetch data via Ajax with initialization parameters, perform initialization logic through class, or bind data directly to the data.

<script> // component export default {props: {data: {type: Object, default: () => ({// list:[], example // total: 10 example }), }, } </script>Copy the code

Configuration parameters for the styleForm component

StyleForm this is the configuration information of the component, such as the background, style information, and fixed data of the component that will not change. The data format type is Object.

<script> // component export default {props: {styleForm: {type: Object, default: () => ({// styles: {}, example // bg: './images/xx.png' example }), }, } </script>Copy the code

CommonStyle commonStyle configuration parameter

The commonForm parameter is a generic style configuration that controls, for example, the width, height, background color of the component. When we independently developed H5, we would configure this parameter format according to this. The goal is to make components more generic and suitable for different places, such as some H5 auto-generation platforms. Because, if the development of H5 is relatively simple in the Internet factory, it will not be developed by manpower alone, but through the configuration of the platform to generate H5. What we need to do is provide a variety of components for business students to configure and use. So, platforms are configured on a per-function basis, and commonForm can access their platform’s interface values to directly control the platform’s width, height, center, and so on.

<script> // component export default {props: {commonStyle: {type: Object, default: () => ({// width: 300, example // height: 20 example }), }, } </script>Copy the code

Global Global attributes

A global attribute is a unique tag for the project that applies anywhere in the project. For example, the id of this item may be required to perform reporting operations or request interfaces. The global parameter is accepted uniformly. The type is also Object.

<script> // component export default {props: {global: {type: Object, default: () => ({// page_id: 111, example }), }, } </script>Copy the code

componentKey

ComponentKey is a component marker used to distinguish components and report data. It can also be used for very specific logic, providing temporary solutions. Take a very simple example: the business side needs to draw 10 circles on a white background, and suddenly asks to add a black dot somewhere in the ninth circle, all else unchanged. The temporary solution to this requirement is to write an if else through the key, and we’ll talk about it later.

<script> // component export default {props: {componentKey: {type: [String,Number], default: 1, // example},} </script>Copy the code

@methods Method binding

In the component through the output button ID or event type, by the upper layer of the component to perform specific logic, such advantages are common style and DOM and JavaScript separation, no business logic can be directly reused next time this component, do not need to change.

component

<template>
    <div class="head-section" style="padding: 0px 0px">
        <div
            class="lottery-btn"
            @click="onClickBtn('lottery', 'normal')"
        ></div>
        <div
            class="nav-btn rule-btn"
            @click="onClickBtn('rule', 'page')"
        ></div>
    </div>
</template>
<script>
export default {
  methods: {
    onClickBtn(id, type = 'page', eventParams = {}) {
      this.$emit('methods',{
        id: type,
        value: eventParams
      });
    },
  },
}
</script>
Copy the code

The parent component

<script> export default { methods: { headMethods($Event) { const { id, value } = $Event; const page = (params) => { this.goPage(); }; const anchor = (params) => { this.goAnchor(); }; const clickEventMap = { 'page': page, 'anchor': anchor, }; ClickEventMap [ID](value); }, goAnchor(params) { // ... }, goPage(params) { // ... } }, } </script>Copy the code

Top of page component

Component layout and implementation:

Component Encapsulation

First of all, from a functional point of view, this component is only suitable for H5 development, it is not suitable for H5 generation platform. Or that such a component would make no sense on an H5 generation platform. Because the icon on the left and the button list on the right are in the H5 generation platform, these buttons are manually configured by the user. For example, the icon on the left is dragged in with a button component, plus a jump event. The bTN-list on the right can be viewed as 3 separate buttons, which are also dragged into by a button component as above, plus a jump event, and configured 3 times consecutively.

This time, however, it was developed independently, so it could only be implemented with the idea of reusable custom templates. The things to consider are:

  1. Header images support background configuration
  2. The buttons on the left support background Settings, text Settings and implicit Settings.
  3. The button list on the right supports background Settings, text Settings and explicit Settings for a single button as well as reusable styles for adding multiple new buttons.

As shown in figure: Each corresponding module uses an ID as a distinction, where bTN-list contains the unique tag bTN-x, the content is to control the background, implicit and text of this button.

Then render the BTN-list with headData in the following format:

<script> export default { data() { return { headData:[ { id: 'btn-1', value: { url: '...' } }, { id: 'btn-2', value: { url: '...' } } .... ]  } }, } </script>Copy the code

The idea is to associate data by ID and configuration by ID. This is a bit like a primary key in a database, which can be used to query or associatively find other tables.

Detail implementation points

Write a generic method to control the position of a new button directly by passing the parameter (button number).

@function head-nav-btn-top($number) {
  $top: 15;
  $boxHeight: 46;
  @if($number= =1) {@return 385;
  }
  @return 385 + (($top + $boxHeight) * ($number -  1));
}
// .class
top: remit(head-nav-btn-top(1));
top: remit(head-nav-btn-top(2));
top: remit(head-nav-btn-top(3));
Copy the code

The anchor point jumps to the position specified by the parameter

headMethods($Event){
  const { id, value } = $Event;
  
  if (id === 'lottery-btn') {
  	const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  	window.scrollTo(scrollTop, this.$refs[value].offsetTop); }},Copy the code

Countdown module

Component layout and implementation:

Component Encapsulation

The countdown component is relatively simple in logic, more need to consider the display style of the countdown, because in H5, each design style or business needs are different, it is impossible to always use a style of countdown, such as this

So the points to consider are:

  1. Various display styles and display forms of the countdown, such as whether the title needs to be spliced and the number of display days or seconds.
  2. The next logical step that needs to be executed at the end of the countdown.

Detail implementation points

There are two ways to calculate the countdown. The first way is to obtain the time of the local phone and write an inteval function to decrement the time. The second way is to use interval to obtain the time from the server every second and calculate the remaining time. I usually choose the latter one because, first of all, the local time may not be correct, or the system time may be artificially changed, and second, the local interval delay may not be accurate. For example, we set the execution to 1000 ms, but due to some reasons of the phone itself, there may be error, set it to 1000 ms, but in the actual execution, it is equivalent to 800 ms. Then it will lead to a problem, the more the local time, the greater the error, if the page time is short that is not a problem, but if the page time is very long, in the end it looks like a big BUG. So it makes sense to read the server time every time.

Key points:

<template> <div class="countdown-section"> <div v-if="styleForm.type === 'normal'" :style="[{ 'background-image': `url(${styleForm.bgUrl})` }]" :class="['countdown-section-bg', `width-${styleForm.bgWidth}`]" > <div class="countdown-content"> <p class="time-front" v-text="styleForm.timeFront"></p>  <p class="time" v-text="countTime"></p> <p class="time-end" v-text="styleFrom.timeEnd"></p> </div> </div> </div> </template> <script> export default { methods: { countdown() { ... this.timeStr = {} //data this.format = ['hours', 'minutes','seconds'] // props for(let i = 0 ; i < format.length; i++ ){ this.timeStr = this.time[format[i]]; } }, }, } </script>Copy the code

Node style: center the content of countle-content, configure any copy before and after the countdown, and add a width to clase=”time”. This advantage is to avoid the jitter problem of the whole countdown copy caused by the switch of the countdown number when the number changes. Another is to wrap a V-IF style in the outer layer of the countdown, which is to expand the function of multiple styles of countdown.

Logical: Pass in a time format configuration item, such as whether days or seconds need to be displayed, and use a loop to specify data updates. The final time is computed by using the computed attribute day, hours minutes, and seconds. Also, a method can be configured when the countdown is zero, for example, the most common action is to refresh the current page or perform a jump.

Progress bar component

Component layout and implementation

Component Encapsulation

The progress bar component, like the countdown component, is one of the more logically simple and style-conscious configurations. The points to consider for the progress bar component are:

  1. The background color supports gradient configuration
  2. The progress bar is configured above and below each node with style support
  3. All contents of a progress bar can be displayed or hidden

Detail implementation points

<template>
<div class="progress-content">
  <div class="progress">
    <div class="progress-line" 
    :style="{ width: `${currentProgress}%`, backgroundImage: `linear-gradient(
    to right,
    ${styles.lineStyle.begin},
    ${styles.lineStyle.end}
    )`}"></div>
    <div class="progress-state">
    	<div v-for="(item, index) in styles.list"
    :key="`${index}buttom`"
    :style="[{ 'background-image': `url(${+index <= +current ? styles.dot.high: styles.dot.normal})` }]"
    class="state"
    >
        <div v-if="item.topText" :style="styles.top[index]" :class="['top']" v-text="item.topText"></div>
        <div v-if="item.bottomText" :style="styles.bottom[index]" class="buttom" v-text="item.bottomText"></div>
    	</div>
    </div>
  </div>
</div>
</template>

<script>
export default {
  methods: {
   
  },
}
</script>
Copy the code

First, get the color of the progress bar using the styles parameter in props. To get more color configuration for the progress bar, use gradients by passing in a start and an end color.

The style and copy of the node are all rendered by array to achieve the purpose of general configuration. The following is a simple configuration data I captured

const progress = {
    top: [{color: '#f5ddff'}, {color: '#d6a5ea',},... ] .bottom: [{color: '#d6a5ea'}, {color: '#d6a5ea',},... ] .dot: {
        high: ' '.normal: ' ',},list: [{topText: 'Switch'.bottomText: '0'.hidden: false
        },
        {
            topText: '2'.bottomText: '10000'.hidden: false},... ] .lineStyle: {begin:'rgba(255, 166, 248, 1)'.end:'rgba(255, 58, 210, 1)'}};Copy the code

Suction bottom components

Component layout and implementation:

Component Encapsulation

The suction bottom component, like the top component, is not suitable for the H5 auto-build platform. The bottom and top components are more like a container in which other components are configured, so this is again a reusable custom template. Some points to consider:

  1. The left most part of the avatar and nickname can be written down and fixed and need to configure the default background state data.
  2. The number of votes in the middle section can be configured to display 1 or 2 lines and support explicit hiding.
  3. The right-most button also supports 1 or 2 buttons and implicit support.
  4. Background color can be configured

Detail implementation points

Their data format is:

<script> export default {data() {return {// Data source bind suspensionData: {userSection: {name: 'XXXX ', URL: '....' PNG ',}, textSection: [{'text': 'contributed votes: 600'}, {'text': 'left votes: 400'}],}, // Set 'suspensionConfig ': {bg: '... PNG ', BTN: [{' id ':' get 'and' url ':'... the PNG ', 'text' : 'access to help ticket'}, {' id ':' exchange ', 'url' : '... the PNG ', 'text' : }]},},},} </script>Copy the code

The text display in the middle and the buttons on the right are rendered as an Array

<template>
...
  <div
    v-if="btn && btn.length > 0"
    :class="['item-right, `length-${btn.length}`']"
    >
    ...
  </div>
</template>
<style lang="scss" scoped>
	... 
	.btn{
		...
		&.length-2{
			justify-content: space-between;
		}
		&.length-1{
			justify-content: space-evenly;
		}
	}
</style>
Copy the code

In the case of length-x configuration of style line is centered or evenly divided style. The same method is used for the middle text, but there are more details to consider, such as overflow handling of the font container and some Settings of line spacing.

Avatar and nickname can also be set, but this is not necessary according to the actual needs, so it is directly fixed here.

Vote components

Component layout and implementation

Component Encapsulation

The voting function of this H5 is relatively simple, with only one increase/decrease and maximum.

Before making this component, I actually wanted it to look like this. As shown in figure:

It can show images, it can show the type of ticket to select, it can also configure the expand button underneath and it can also bind the execution event, it looks very good. However, on second thought, I still think that the logic of the voting component will be a little redundant. Since it is a voting component, there should be other things.

So I also combined this component on the original basis to add a voteType selection. Here it is:This seems logical and simple, and it’s actually a more practical feature. So, the points to consider for this component are:

  1. Increase/decrease and maximum calculation
  2. Type selection can be extended and hidden by default.

Detail implementation points

<template> ... <! <div class="ticket-section"> <div class="ticket-edit"> <input class="ticket-text" v-model="ticketInfo['count']"/> <div :class="['ticket-add']" @click="ticketAdd()" > <p class="add">+</p> </div> <div :class="['ticket-min']" @click="ticketMins"> <p class="min">-</p> </div> </div> <div :class="['ticket-max', 'allow']"> <p class="max" @click="ticketAdd(true)">MAX</p> </div> </div> <! --> <div class="ticket-type-section"> <div v-if=" typelist. length > 0" class="ticket-type-content"> <div v-for="(item,index) in typeList" :key="`type${index}`" class="ticket-type-item"> <div :class="['box',item.active? 'active': '']"></div> <div v-text="item.text"></div> </div> </div> </div> ... </template>Copy the code

First render the list of types with an array, edit the area vote area is more important is to do a good number of checksum and unified management check failure prompt text.

const tipsMap = {
    error: 'kiss! The remaining power ticket is insufficient, please re-enter! '.success: 'Help to succeed! '.errorNum: 'Must be a number, no Spaces.'.errorMax: 'Dear, there are not enough tickets left, please re-enter'.errorZero: 'Dear, the remaining ticket is not enough, please go to get oh! '};const validCount = (num) = > {
this.$set(this.'showTips'.false);
  const regExp = / ^ \ +? [1-9][0-9]*$/g;
  if (+this.ticketInfo.count === 0) {
   return false
  }
  if(! regExp.test(this.ticketInfo.count)) {
  	this.toast(tipsMap['errorNum'])
  	return false;
	};
  if (+this.ticketInfo.count > +this.ticketInfo.left){
  	this.toast(tipsMap['errorMax'])
  	return false;
  };
  // Check passed
  return true;
  }
},
validCount(1000);
Copy the code

Leaderboard component

Component layout and implementation

Component Encapsulation

The ranking component is the most complicated one in this activity. It needs to support the list data displayed in this activity as well as other H5 data display in the future, which is to support expansion. For example, in this leaderboard, the first column is an avatar list type, the second column is a text type, the third column is an avatar type, and the fourth column is a button type. Then, the type and style of each column is defined through the Config configuration at component initialization time. As shown in figure:

const rankConfig = {
    init: [{type: 'headList'.key: 'head'.name: 'super powers'.tips: 'live'.style: {
              width: '25%'.color: '#ffffff'.background: '#c69494',}}, {type: 'text'.key: 'score'.name: 'Total power'.style: {
              width: '25%'.color: '#ffffff'.background: '#e53de7',}},... ] };Copy the code

This section is part of the configuration

  1. Type is a data type, such as plain text, picture, or button.
  2. The key is the key that corresponds to the actual data, and that allows the column to display the contents of the field.
  3. Name is the title of this column
  4. Style is the specific style configuration parameter for this column.

In the future, no matter what kind of a leaderboard is needed, first look up the document to see if there is such a type of style, no words to expand, if there is only need to pass in configuration parameters, and then pass in specific data can run a leaderboard component.

Functionally, it needs support:

  1. Support data paging
  2. Support data presentation and extended data types for presentation
  3. Click Event General Configuration

Detail implementation points

The main code of the title part:

<template>
...
<div v-for="(item, index) in styles.init"
     :style="item.style"
     :class="['column-item', 'column-type']"
     :key="`${index}column`">
     
	<div class="item-title">
		
    <p class="title-text" v-text="item.name"></p>
  	<slot class="title-tips" :name="`sub-${item.key}`"></slot>
  </div>
</div>
</template>
Copy the code

Sub -${item.key} is used as a distinction and the title of tips icon should be displayed. We can only use key here and not type because it is possible to have the same type column in a list.

List rendering, where various types of presentation are needed to be separated into a widget, such as text, headList, etc., to be separated into a widget and referenced as needed. The advantages of this are that the logical separation is easy to maintain, the small parts are easy to expand, and the leaderboard code is not too much, as shown in the figure:His core code is as follows:

<template> ... <div class="column" v-for="(item, index) in info.list" :key="`${index}rankList`" > <div v-for="(styleItem, styleIndex) in styles.init" :key="`${styleIndex}rankConfig`" :class="'column-item'" > <HeadList v-if="styleItem.type ===  'headList'" @methods="onClickEvent(item.key, item)" ><HeadList> <Text v-if="styleItem.type === 'text'"></Text> <ListBtn v-if="styleItem.type === 'btn'"></ListBtn> </div> </div> </template>Copy the code

The first loop iterates through all the list data, the second loop iterates through the configuration table, rendering the details by type, and then the contents of each block are imported as a widget.

These are some of the core logic and thinking behind this H5 componentized development. Emm is going to post these big H5 screenshots in an issue as a memory.