In most web products, global message components are widely used. They often appear in scenarios for user feedback, information prompts, and dialogues with the system. If you use the traditional component writing method, you need to import the component and register it in components, then call it in the template in the form of a tag, pass in custom props attributes, and trigger events through emit.
What we want the component is easy to use, just call a method.
Message.text("This is default message");
Message.error({
content: "error message",
duration: 3000,
close: true
});
Component API Design
// Message type:
["text", "success", "error"]
// Message option
[String]: message content
[Object]: message config
text [String] "" message content
duration [Number] 0 milliseconds to close, 0 means it will not close automatically.
close [Boolean] false show close icon or not
// call api
Message[type](option);
Component File Structure
|--- message
|--- src
| |--- style
| | |--- index.less
| |--- message-item.vue // component template of one message
| |--- message.js // render the component instance
| |--- Instance.js // component instance
|---index.js // export component
Template
The template is simple. The outer layer is wrapped by an animation component, and the message display and closing are controlled by v-show. The content part includes an icon, message text, and a configurable manual close button.
<template>
<!-- message list -->
<Transition name="slide-fade">
<div class="message-container" v-show="visible">
<!-- content -->
<div class="message-content">
<!-- Icon is determined by the message type. No icon is configured for text type -->
<div class="message-icon" v-if="config.icon">
<i :class="config.icon"></i>
</div>
<!-- message text -->
<span v-text="config.content"></span>
<!-- click to close -->
<div class="option" v-if="config.close">
<i class="ri-close-fill" @click="onClose"></i>
</div>
</div>
</div>
</Transition>
</template>
message icon
The icon is determined by the type in the API call. The icon type is determined when creating the instance. Here, the open-source icon library Remix Icon is referenced. You can visit: remixicon.cn
Style
Create styles and animations.
@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 0.4s ease;
.message-container {
margin-bottom: 14px;
.message-content {
display: inline-block;
padding: 0 18px;
height: @normalHeight;
text-align: left;
line-height: @normalHeight;
font-size: 14px;
font-weight: 400;
border-radius: @radius;
color: #fff;
background: #000;
.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.2s ease;
&:hover {
color: #ff7c75;
transition: color 0.2s ease;
}
}
}
}
.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;
width: 20px;
height: 20px;
background-color: red;
-webkit-mask-image: url('close-circle-fill.svg');
mask-image: url('close-circle-fill.svg');
}
}
}
}
.slide-fade-enter-active {
transition: all 0.2s ease-out;
}
.slide-fade-leave-active {
transition: all 0.2s ease;
}
.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateY(-50px);
opacity: 0;
}
template script
In the component, rendering and unmounting are implemented by obtaining the passed-in config configuration and remove. The onOpen and onClose methods are used to control message opening and manual closing. The specific code is as follows.
<script setup>
import { ref } from 'vue';
import './style/index.less';
const props = defineProps({
config: { type: Object, default: () => undefined }, // message config
remove: { type: Function, default: () => undefined } // callback of remove message
});
const visible = ref(false);
const onClose = () => {
visible.value = false;
setTimeout(() => {
props.remove();
}, 200);
};
const onOpen = (config) => {
setTimeout(() => {
visible.value = true;
}, 10);
// remove message in duration
if (config.duration !== 0) {
setTimeout(() => {
onClose();
}, config.duration);
}
};
onOpen(props.config);
</script>
Create Component Instance
Next, in Instance.js, we will write APIs for creating, mounting, and destroying components when the component is called. At the top, import the method for creating a Vue instance and the component template we just wrote:
import { createApp } from 'vue';
import MessageItem from './message-item.vue';
Declare instance operation methods, accepting a message configuration parameter, cfg.
/**
* Message
* @param {Object} cfg
*/
const createInstance = cfg => {
const config = cfg || {}
// 1、Create a wrapper container, and set the outer Class attribute and message count.
// 2、Create an instance and mount it to the body.
// 3、unmount method, and recount after unmounting.
}
export default createInstance
1. Create a wrapper container and set the outer Class attribute
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)
Message count, we define the height of a message pop-up as 54 px. When multiple messages are queued to open, we make each component staggered by setting the top value.
const height = 54 // Height of a single message box
const messageList = document.getElementsByClassName('message')
messageNode.style.top = `${messageList.length * height}px`
2. Create an instance and mount it to the body.
const app = createApp(MessageItem, {
config,
remove() {
handleRemove()// Remove the element. After the message is closed, unmount and remove it from the DOM.
}
})
// Mount the instance and append it to the end of the body
app.vm = app.mount(messageNode)
document.body.appendChild(messageNode)
app.close = () => {
handleRemove()
}
return app
3. Define methods for unmounting and resetting the top value.
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`
}
}
API for rendering instances
Read the configuration and render in message.js.
import createInstance from './instance.js';
/**
* Read configuration and render Message
* @param {Object} typeCfg message type
* @param {Object/String} cfg config
*/
function renderMsg(typeCfg = {}, cfg = '') {
// Allow direct input of message content,
// therefore, it is necessary to determine the type of the passed-in cfg
const isContent = typeof cfg === 'string';
// Integrate custom configurations
cfg = isContent
? {
content: cfg
}
: cfg;
const config = Object.assign({}, typeCfg, cfg); // 合并配置
const {
type = 'text', // message type
content = '', // message content
icon = '', // message icon
duration = 3000, // Automatic closing delay time
close = false // Whether to display the close button
} = config;
// create instance
return createInstance({
type,
content,
duration,
icon,
close
});
}
Expose APIs such as text, success, error, etc.
export default {
// text type
text(cfg = '') {
const textCfg = {
type: 'text',
icon: ''
};
return renderMsg(textCfg, cfg);
},
// success type
success(cfg = '') {
const successCfg = {
type: 'success',
icon: 'ri-checkbox-circle-fill'
};
return renderMsg(successCfg, cfg);
},
// error type
error(cfg = '') {
const errorCfg = {
type: 'error',
icon: 'ri-close-circle-fill'
};
return renderMsg(errorCfg, cfg);
}
};
Finally, expose this component in the index.js.
import Message from './src/Message.js';
export default Message;
Checkout out full code: https://github.com/markliu2013/StellarNovaUI/tree/main/packages/components/src/message
After understanding the code, You can style and change icon base on your project.
Top comments (0)