In most Web products, the global Message component occupies a large usage scenario, often in the context of giving user feedback, Message prompts, and conversations with the system. If you use traditional component writing, you need to import the component, register it in components, and then call it as a tag in the template, passing in custom props and firing an event through the emit. This type of component has the following disadvantages:

  • Frequent introduction and registration is required
  • Components need to be used as labels in a template
  • Additional parameters are required to control the properties and state of the component
  • The mount location of an uncustomizable component may be affected by other components

So for a component like Message, we want to be able to call it in JavaScript, pass in custom parameters to control the state of the component, and not have to manually mount the component to the end of the body when called. If you’ve used major third-party libraries such as ElementUI Plus or Ant Design for Vue, you’re familiar with their Message component apis, so let’s work together to implement a global Message component using Vue3.

Component final implementation effect

Component design

Define the final component API

Implement a simple Message component with type apis such as text, Success, and error, which support passing in text directly or through component-specific option configuration. From defining the message content, the close delay, and whether to display the close button.

// Message type (type) : text, success, or failure
["text"."success"."error"]

// Message (option)
[String]: Message content [Object]: Message configuration/ / configuration option
text [String] ""Duration message content [Number] 0The number of milliseconds of automatic closing delay,0To not automatically close close [Boolean] falseWhether to display the close button// Call mode
Message[type](option);
Copy the code

Invoke the sample

Message.text("This is a message tip.");
Message.error({
    text: "Network error, please try again later".duration: 3000.close: true
});
Copy the code

Defining the component structure

Create a Message folder to store the overall structure of the component, where SRC contains the component’s templates, styles, and instance files, and, at the same level, create index.js to expose the entire component for inclusion in project and business components.

|--- Message
	|--- src
	|	|--- Message.vue // Component templates
	|	|--- Message.less // Provide component style support
	|	|--- Message.js // Read the configuration and render the component instance
	|	|--- Instance.js // Component instance
	|---index.js // Expose components
Copy the code

Templates and Styles

Template Template

The template is relatively simple and is wrapped in an animation component that controls message display and closing through v-show. The content section includes ICONS, message text, and a configurable manual close button.

<template>
  <! -- Message list -->
  <transition name="slide-fade">
    <div class="message-container" v-show="visibled">
      <! - content - >
      <div class="message-content">

        <! -- The icon of the message type is determined by the message type. The icon of the text type is not configured -->
        <div class="message-icon" v-if="config.icon">
          <i :class="config.icon"></i>
        </div>

		<! -- Message text -->
        <span v-text="config.content"></span>

        <! -- Turn off messages manually -->
        <div class="option" v-if=! "" config.close">
          <i class="ri-close-fill" @click="onClose"></i>
        </div>

      </div>
    </div>
  </transition>
</template>
Copy the code

The message icon

It is important to note that the Icon is determined by the type in the calling API. The Icon type is determined when the instance is created. This is the open source Icon library Remix Icon.remixicon.cn/

style

Define styles and animations in message.less.

@radius: 4px;
@normalHeight: 34px;

.message {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  text-align: center;
  box-sizing: border-box;
  z-index: 9999;
  transform: translateZ(9999px);
  padding-top: 28px;
  transition: top .4s ease;

  .message-container {
    margin-bottom: 14px;

    .message-icon {
      display: inline-block;

      i {
        font-size: 18px;
        font-weight: 400;
        margin-top: -3px;
        margin-right: 6px;
        display: inline-block;
        box-sizing: border-box;
        vertical-align: middle;
      }

      .ri-checkbox-circle-fill {
        color: #58c05b;
      }

      .ri-close-circle-fill {
        color: #fd4f4d;
      }

    .message-content {
      display: inline-block;
      padding: 4px 18px;
      height: @normalHeight;
      text-align: left;
      line-height: @normalHeight;
      font-size: 14px;
      font-weight: 400;
      border-radius: @radius;
      color: # 595959;
      box-shadow: 0 4px 12px rgba(0.0.0.15);
      background: #ffffff;

      .option {
        display: inline-block;
        pointer-events: all;
        margin-left: 18px;

        i {
          font-size: 18px;
          font-weight: 400;
          margin-top: -3px;
          display: inline-block;
          box-sizing: border-box;
          vertical-align: middle;
          cursor: pointer;
          color: #d9d9d9;
          transition: color 0.2 s ease;

          &:hover {
            color: #ff7c75;
            transition: color 0.2 s ease;
          }
        }
      }
    }
  }

  .slide-fade-enter-active {
    transition: all .2s ease-out;
  }

  .slide-fade-leave-active {
    transition: all .2s ease;
  }

  .slide-fade-enter-from..slide-fade-leave-to {
    transform: translateY(-20px);
    opacity: 0; }}Copy the code

Component script

The component implements rendering and unmounting by obtaining the incoming config configuration and remove. The onOpen and onClose methods control the opening and manual closing of messages. The specific code is as follows:

<script>
import { reactive, toRefs } from "vue";
export default {
  props: {
    config: { type: Object.default: () = >{}},// Message configuration items
    remove: { type: Function.default: () = >{}},// Cancel the mount callback
  },
  setup(props) {
    const state = reactive({
      visibled: false,})// Open the message
    const onOpen = (config) = > {
      setTimeout(() = > {
        state.visibled = true;
      }, 10)

      // Remove the message after the specified time
      if(config.duration ! = =0) {
        setTimeout(() = > {
          onClose();
        }, config.duration);
      }
    }

    onOpen(props.config)

    // The message is closed
    const onClose = () = > {
      state.visibled = false;
      setTimeout(() = > {
        props.remove()
      }, 200)};return{... toRefs(state), onOpen, onClose, }; }}; </script>Copy the code

Creating a component Instance

In instance.js, we will write apis for creating, mounting, and destructing components when a component is called. In the header, we will introduce Vue’s create Instance method and the component template written above:

import { createApp } from 'vue'
import Message from './Message.vue'
Copy the code

Declare the instance action and accept a message configuration parameter CFG

/** * Message instance operation *@param {Object} CFG instance configuration */
const createInstance = cfg= > {
	const config = cfg || {}
	// 1, create a container, and set the outer Class attribute, message count

	// 2. Create an instance and mount it to the body
	
	// 3. Realize the method of canceling mount, and re-count after canceling mount
}
export default createInstance
Copy the code

1, create a wrapper container and set the Class attribute for the outer layer

Create a DIV as an outer container to wrap the component and set the corresponding class attribute

let messageNode = document.createElement('div')
let attr = document.createAttribute("class")
attr.value = "message"
messageNode.setAttributeNode(attr)
Copy the code

Message count, we define a message box height of 54 px, when multiple messages are queued open, by setting the top value to stagger the components.

const height = 54 // The height of a single message box

const messageList = document.getElementsByClassName('message')
messageNode.style.top = `${messageList.length * height}px`
Copy the code

2, create an instance and mount it to body

const app = createApp(Message, {
  config,
  remove() {
    handleRemove()// Remove the element. When the message is closed, unmount and remove it from the Dom}})// Mount the instance and append it to the end of body
app.vm = app.mount(messageNode)
document.body.appendChild(messageNode)

app.close = () = > {
  handleRemove()
}

return app
Copy the code

3. The methods for unmounting and resetting the top value are defined

const handleRemove = () = >{
  app.unmount(messageNode)
  document.body.removeChild(messageNode)
  resetMsgTop()
 }

const resetMsgTop = () = > {
  for (let i = 0; i < messageList.length; i++) {
    messageList[i].style.top = `${i * height}px`}}Copy the code

Implement the rendering instance API

Message.js reads the configuration and renders it.

import createInstance from './Instance.js'

/** * read the configuration and render Message *@param {Object} TypeCfg Type configuration *@param {Object/String} CFG User-defined configuration */
function renderMsg(typeCfg = {}, cfg = ' ') {
  // Direct incoming message content is allowed, so determine the type of CFG that is passed in
  const isContent = typeof cfg === 'string'

  // Integrate custom configurations
  cfg = isContent ? {
    content: cfg
  } : cfg

  const config = Object.assign({}, typeCfg, cfg) // Merge the configurations

  const {
    type = 'text'.// Message type
    content = ' '.// Message content
    icon = ' '.// Message icon
    duration = 3000.// Automatically close the delay time
    close = false // Whether to display the close button
  } = config

  // Create an instance
  return createInstance({
    type,
    content,
    duration,
    icon,
    close
  })
}
Copy the code

exposedtext,success,errorSuch as API.


export default {
  // A plain text message
  text(cfg = "") {
    const textCfg = {
      type: "text".icon: ' '
    }

    return renderMsg(textCfg, cfg);
  },
  // Success
  success(cfg = "") {
    const successCfg = {
      type: "success".icon: 'ri-checkbox-circle-fill'
    }

    return renderMsg(successCfg, cfg);
  },
  // Error message
  error(cfg = "") {
    const errorCfg = {
      type: "error".icon: 'ri-close-circle-fill'
    }

    returnrenderMsg(errorCfg, cfg); }},Copy the code

Finally, open the component for invocation in the outermost index.js.

import Message from './src/Message.js';

export default Message;
Copy the code