preface
To say theToast components
Is an essential component of a project, a good-looking Toast prompt can give users a good experience, this article we will implement ElementUIMessage
Message prompt component.
Let’s start with the renderings. (this… Can I keep you?
Front knowledge
- Basic Vue knowledge.
Vue.extend()
Methods.portalvm.$mount()
Methods.portal
Also don’t understand,Vue.extend()
withvm.$mount()
?
Don’t understand the official website? That’s all right. Please look down.
First we know how to three concepts: Vue constructor > Vue component > Vue instance. Their basic structure is as follows:
// Vue constructor Vue. Extend ({template: '<div></div>', data: function() {return {}}}); Vue.com ('componentName', {template: '<div></div>', data: function() {return {}}}); Vue.com ('componentName', {template: '<div></div>', data: function() {return {}}); // Vue instance new Vue({el: ", data: {}});Copy the code
Vue components and Vue instances will not be discussed, you know, so easy~
The key is what is the Vue constructor? The constructor? What is it constructed? The Vue constructor (vue.extend (options)) is a compiler that constructs components. When you give it an argument, it returns a component. The options it receives are basically the same as the Vue instance arguments.
The generated components can be registered to mount to a Vue instance for use.
var toast = Vue.extend({
template: '<div></div>',
data: function() {
return {}
}
});
Vue.component('toast', toast);
Copy the code
Alternatively, we can register the component within the Vue instance via the Components property.
new Vue({
components: { toast: toast}
})
Copy the code
Alternatively, we can use vm.$mount() to manually mount(tap blackboard!!). .
var toast = Vue.extend({
template: '<div></div>',
data: function() {
return {}
}
});
new toast().$mount('#app');
Copy the code
$mount() allows you to manually mount components using the vm.$mount() function to control whether components are rendered or mounted using the JS logic. This opens the door for a later implementation to call the component (this.$toast()) from JS.
vm.$mount()
If there are no arguments, the component is rendered but not yet mounted to the page. If there are correct elements as arguments, the component is mounted directly below the element.- Different Vue components can have the same Vue constructor, and different Vue instances can share the same Vue component. (Vue constructor > Vue Component > Vue instance.)
The body of the
Demand analysis
With a quick analysis, weMessage
The component will roughly function as shown below, which meets the requirements of the Toast prompt in the project and is roughly consistent with ElementUI.
Implementation one – infrastructure and Styles
With a rough idea of what we’re going to do, let’s get down to business. Using vue-CLI to initialize a project directly, we first put the structure and style to dry out, directly on the code.
<template> <div class="yd-message"> <div class="yd-message__content"> <img :src="icons.infoIcon" Class ="yd-message__icon" /> <p class="yd-message__text"> test test </p> </div> <img: SRC =" ICONS. InfoCloseIcon" class="yd-message__close-btn"/> </div> </template> <script> import infoIcon from './images/infoIcon.png' import infoCloseIcon from './images/infoCloseIcon.png' import successIcon from './images/successIcon.png' import successCloseIcon from './images/successCloseIcon.png' import errorIcon from './images/errorIcon.png' import errorCloseIcon from './images/errorCloseIcon.png' import warningIcon from './images/warnIcon.png' import warningCloseIcon from './images/warnCloseIcon.png' export default { name: 'YdMessage', data() { return { icons:{ infoIcon, infoCloseIcon, successIcon, successCloseIcon, errorIcon, errorCloseIcon, warningIcon, warningCloseIcon } } } } </script>Copy the code
The complete style of the Message component.
<style scoped>. Yd-message {width: 380px; min-height: 44px; position: fixed; z-index: 10000; top: 20px; left: 50%; margin-left: -190px; box-sizing: border-box; padding: 0 8px 0 10px; Background: rgba (156212255,0.72); Border: 1 px solid rgba (156212255, 1); border-radius: 5px; overflow: hidden; transition: opacity .3s, transform .4s, top .4s; display: flex; align-items: center; } .yd-message__content{ overflow: hidden; display: flex; align-items: center; flex: 1; } .yd-message__icon{ min-width: 32px; width: 32px; height: 32px; } .yd-message__text{ color: #fff; font-size: 14px; } .yd-message__close-btn{ min-width: 32px; width: 32px; height: 32px; cursor: pointer; }. Yd - message. Success {background: rgba (13206127,0.72); Border: 1 px solid rgba (13206127, 1); }. Yd - message. The error {background: rgba (255,68,68,0.72); Border: 1 px solid rgba (255,68,68,1); }. Yd - message. Warning {background: rgba (254132,86,0.72); Border: 1 px solid rgba (254132,86,1); } .ani_yd-message-fade-enter, .ani_yd-message-fade-leave-active { opacity: 0; transform: translate(0, -100%) } .center{ justify-content: center; } </style>Copy the code
Let’s introduce it briefly and see what it looks like.
Implementation of two & [Manual control of registered components]
Now that we’re done styling, let’s consider the second and more critical question, which is to use vue.extend () and vm.$mount() for button control of the Message component’s appearance and disappearance.
Let’s look at the form in which ElementUI calls the Message component: this.$Message (‘ This is a Message prompt ‘);
From the above information, we can roughly observe the following two information:
- We need to mount a $message method on this, that is, a method on the Vue prototype (vue.prototype) for easy calls.
- The method takes an argument, and we do it based on that information
Message
Component rendering, display, destruction and a series of processes.
Let’s start with the change in project structure.
-
Create a YDUI folder and create an index.js file at the bottom of it as a unified file for all components. If you have another component, you can still create another folder under the YDUI folder and introduce it in index.js.
We import the index.js file into main.js as follows:
// main.js import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = Import YDUI from '@/components/YDUI'; Vue.use(YDUI) new Vue({ router, render: h => h(App) }).$mount('#app')Copy the code
-
Here using the Vue. Use (Function | Object) method to complete the registration, the method is simple say is:
- If the argument is a function it is executed immediately, and by default the first argument is a Vue instance.
- If the argument is an object, it must be provided in the object
install()
Method, which is also executed, is also injected into the Vue instance by default.
-
Vue. Prototype.$message = message.service; You may be wondering what message.service is. If you look more closely, it’s actually Message () as defined in Message/code/main.js.
// YDUI/index.js import Message from './Message'; const install = function(Vue, opts = {}) { Vue.prototype.$message = Message.service; // Mount method}; /* istanbul ignore if */ if(typeof window ! == undefined && window.Vue) { install(window.Vue); } export default { install }Copy the code
-
-
Create an index.js file in the Message folder of the Message component as the only exit for the component.
// YDUI/Message/index.js import service from './code/main.js'; export default { service }; Copy the code
-
Put the core code of the Message component in the code folder. The main. Vue file remains the same structure and style as above, focusing on the main.js file.
// YDUI/Message/code/main.js import Vue from 'vue'; import Main from './main.vue'; const messageConstructor = Vue.extend(Main); let instance = null; let instances = []; Const message = (options = {}) => {// instantiate instance = new messageConstructor(); $mount() = $mount(); $mount() = $mount(); / / add components to the body of the page document. The body. The appendChild (instance. $el); // Save each component instance instance.push (instance); // Each call to this.$message() returns an instance of the corresponding component, so that subsequent operations can return instance}; export default messageCopy the code
The message() function above does only one thing: manually mount the component under the body to prevent the component from being lost after the router-view/> refresh.
-
Now let’s test that writing this way can dynamically mount the component. No, let’s remove the original registration method and give the button click event to call the Message component.
// view/Home.vue methods: { onClick() { this.$message() } } Copy the code
Look at the GIF above and notice the changes in the console. Clicking on the button can actually add a dynamically mounted component, and each click will mount a component
fiexd
The layout is stacked, but you can see that the DOM nodes under the body keep growing.
Of course, we haven’t turned off the destruction function yet, but we’ve taken the first step, so let’s keep moving the bricks.
Achieve three & [spacing, manual close, automatic hiding]
After these two steps, most of the work we have left is just two files in Message/code.
Above we made the component visible, so now it’s time to kill it, but in order to see the effect, we first add a bit of space for each component, but be aware that the distance from the top of each component should be different, so we should give each component a unique ID to distinguish it.
// main.vue <template> <div :style="positionStyle" class="yd-message"> ... </div> </template> <script> ... export default { data() { return { ... , offset: 20 // Default offset}}, computed: {positionStyle() {return {'top': '${this.offset}px'}; } }, } </script>Copy the code
The main.js file does two things: give the instance a unique ID and calculate the overall offset and the offset for each component.
// main.js import Vue from 'vue'; import Main from './main.vue'; const messageConstructor = Vue.extend(Main); let instance = null; let instances = []; let seed = 1; Const message = (options = {}) => {// Merge parameters passed in with data instance = new messageConstructor({data: Options // will automatically merge with data in main.vue}); // let id = 'myMessage_' + seed++; instance.id = id; instance.$mount(); document.body.appendChild(instance.$el); / / at the top of the offset distance let offset = options. The offset | | the instance. The offset. ForEach (item => {offset += item.$el.offsetheight + 16); instance.offset = offset; instances.push(instance); return instance }; export default messageCopy the code
Rendering.
And then what we’re going to do is we’re going to try to kill those components, and we’re going to default to three seconds before the components disappear, and we’re going to look at the code changes. Because it is automatic disappearance, we must have a timer (setTimeout), and the operation of closing is written as a separate method, which is convenient to call it directly when manually closing later.
// main.vue <template> <div v-if="visible" :style="positionStyle" class="yd-message"> ... </div> </template> <script> ... export default { data() { return { ... , visible: false, // Control components to show and hide tokens duration: 3000, // Default hiding time onClose: null, // Callback function when closed}}, computed: {... }, mounted() {this.starttimer (); }, methods: {close() {this.visible = false; if (typeof this.onClose === 'function') { this.onClose(this); // After the component is destroyed, We call onClose(), which is mounted in main.js, because we saved each component instance in main.js, and now the component is destroyed, these instances have to be disposed of to prevent memory usage. clearTimer() { clearTimeout(this.timer); }, StartTimer () {if (this.duration > 0) {this.timer = setTimeout(() => {if (this.visible) {this.close()} }, this.duration); } } } } </script>Copy the code
$message(‘ this is a message prompt ‘); And then you have to do the three things you need to do after the component is hidden. What are the three things? Hey, look down here.
Import Vue from 'Vue '; import Main from './main.vue'; var messageConstructor = Vue.extend(Main); let instance = null; let instances = []; let seed = 1; Const message = (options = {}) => {const message = (options = {}) => {if(typeof options === 'string') {options = {message: options } } instance = new messageConstructor({ data: options }); let id = 'myMessage_' + seed++; instance.id = id; // Let optionsOnClose = options.onclose; // Mount the onClose() method in the instance's data property, and wait for the closing event to trigger a subsequent callback to handle the rest. instance.onClose = () => { message.close(id, optionsOnClose); }; instance.$mount(); document.body.appendChild(instance.$el); let offset = options.offset || instance.offset; instances.forEach(item => { offset += item.$el.offsetHeight + 16 }); instance.offset = offset; // display dom instance.visible = true; instances.push(instance); return instance }; /** * What needs to be done after the current component is hidden: * 1. Handle the offset of other components moving up * 2. * @param id; * @param id; Close = function(id, optionsOnClose) {let len = instances. Length; let index = -1; for (let i = 0; i < len; i++) { if (id === instances[i].id) { index = i; if (typeof optionsOnClose === 'function') { optionsOnClose(instances[i]); } instances.splice(i, 1); break; }} / / multiple shut down the middle, to the following up the if (len < = 1 | | index = = = 1 | | index > instances. The length of 1) return; const removedHeight = instances[index].$el.offsetHeight; for (let i = index; i < len - 1 ; i++) { let dom = instances[i].$el; dom.style['top'] = parseInt(dom.style['top'], 10) - removedHeight - 16 + 'px'; }}; export default messageCopy the code
Js file is temporarily complete, happy, flower flower flower ^-^.
And then we’re going to look at the hidden effects, and notice here because it’s usedv-if
So wouldn’t it be nice to see that the console node is also deleted when the component disappears?Finally, let’s take a look at the manual closing, which is actually relatively easy, so we can change the code directly.
$message({duration: 0}); // view/ home. vue methods: {onClick() {$message({duration: 0}); }}Copy the code
Register events directly for closed ICONS
// main.vue
<template>
<div v-if="visible" :style="positionStyle" class="yd-message">
...
<img @click="close" :src="icons.infoCloseIcon" class="yd-message__close-btn"/>
</div>
</template>
...
Copy the code
Rendering.
Achieve four & [Entrance animation, switch to different themes]
Message
The component is still a bit stiff when it appears, let’s give it a transition effect, using Vue<transition/>
To implement.
<template>
<transition name="ani_yd-message-fade" >
<div v-if="visible" :style="positionStyle" class="yd-message">
...
</div>
</transition>
</template>
<style scoped>
.ani_yd-message-fade-enter, .ani_yd-message-fade-leave-active {
opacity: 0;
transform: translate(0, -100%)
}
</style>
Copy the code
Message
There are four themes, and switching themes is just a matter of changing different class names and ICONS.
// view/Home.vue methods: { onClick() { this.$message({ type: 'success' }); }}Copy the code
The main vue file
// main.vue <template> <transition name="ani_yd-message-fade" > <div :class="[type]" v-if="visible" :style="positionStyle" class="yd-message"> ... <img @click="close" :src="icons[type + 'CloseIcon']" class="yd-message__close-btn"/> </div> </transition> </template> <script> import infoIcon from '.. /images/infoIcon.png' import infoCloseIcon from '.. /images/infoCloseIcon.png' import successIcon from '.. /images/successIcon.png' import successCloseIcon from '.. /images/successCloseIcon.png' import errorIcon from '.. /images/errorIcon.png' import errorCloseIcon from '.. /images/errorCloseIcon.png' import warningIcon from '.. /images/warnIcon.png' import warningCloseIcon from '.. /images/warnCloseIcon.png' export default { data() { return { icons:{ infoIcon, infoCloseIcon, successIcon, successCloseIcon, errorIcon, errorCloseIcon, warningIcon, warningCloseIcon }, ... }},... } </script>Copy the code
The source code
Finally, we will post the complete HTML and JS code of main.vue (the complete style code is above).
<template> <transition name="ani_yd-message-fade" > <div v-if="visible" :class="[type]" :style="positionStyle" class="yd-message" @mouseenter="clearTimer" @mouseleave="startTimer"> <div class="yd-message__content" :class="{'center': center}"> <img v-if="['info', 'success', 'error', 'warning'].includes(type)" :src="icons[type + 'Icon']" class="yd-message__icon" /> <p class="yd-message__text" v-text="message" /> </div> <img v-if="showClose" @click="close" :src="icons[type + 'CloseIcon']" class="yd-message__close-btn"/> </div> </transition> </template> <script> import infoIcon from '.. /images/infoIcon.png' import infoCloseIcon from '.. /images/infoCloseIcon.png' import successIcon from '.. /images/successIcon.png' import successCloseIcon from '.. /images/successCloseIcon.png' import errorIcon from '.. /images/errorIcon.png' import errorCloseIcon from '.. /images/errorCloseIcon.png' import warningIcon from '.. /images/warnIcon.png' import warningCloseIcon from '.. /images/warnCloseIcon.png' export default { name: 'YdMessage', data() { return { icons:{ infoIcon, infoCloseIcon, successIcon, successCloseIcon, errorIcon, errorCloseIcon, warningIcon, warningCloseIcon }, closed: false, timer: null, visible: false, type: 'info', showClose: False, message: ", duration: 3000, offset: 20, // Top offset onClose: NULL, center: false}}, computed: { positionStyle() { return { 'top': `${ this.offset }px` }; } }, watch: { closed(newVal) { if (newVal) { this.visible = false; } } }, mounted() { this.startTimer(); }, methods: { close() { this.closed = true; if (typeof this.onClose === 'function') { this.onClose(this); } }, clearTimer() { clearTimeout(this.timer); }, startTimer() { if (this.duration > 0) { this.timer = setTimeout(() => { if (! this.closed) { this.close() } }, this.duration); } } } } </script>Copy the code
After the above four implementation process basically the Message component of the core implementation of the finished, the rest of the small function of the improvement will not continue to talk, because the rest are very simple, their own understanding, ha ha, specific can refer to the source code to see it. portal
Shout, finally finished, the first time to spend how long energy to write an article, is to write a copy, is to write code, but also have to do the effect map. Last hope I beep beep beep, say what east east can friends understand, as long as can help a person, this article is to realize the value of it, I will meet, ha ha ~