Writing in the front

The project address

👉👉 Project preview address, which can be directly set as the browser home page or desktop shortcut for use

The source address

Fully open source, you can study, secondary development. Of course, we are very welcome to click Star⭐⭐⭐ 👉👉 source link (gitee).

Introduction to the

This article documented some of the basic components of the Warbler bookmark package, including

Button

Overlay

Dialog

Message

Since this project is for practice, some functions that are not actually used may be added to some components. Next, the properties, methods and some design ideas of these components will be introduced one by one.

In addition to some special styles, the article will not post a large number of CSS code, because my SASS knowledge is not very good,CSS code looks more bloated 😣😣😣, interested students can check the source code

The directory structure

It is basically a component. Vue file and a corresponding index. Ts, which holds basic data, type declarations, etc

├ ─ ─ the SRC ├ ─ ─ assets// Store static resources├ ─ ─ baseComponents// Base component└ ─ ─ the Button// Button component│ └ ─ ─ Button. Vue └ ─ ─ the Message// Popover component├ ─ ─ Message. Vue └ ─ ─ but ts...Copy the code

Button

show

So if you look at the effect, you can see that there’s an animation when the mouse moves in and out

attribute

  • The title button text
  • BackgroundColor The color the mouse moves in
  • UseAnimation Whether to enable animation

Design ideas/highlights

The backgroundColor of the 🌝 button uses vue3’s props variable backgroundColor. If you don’t know the new feature, go to the official website to check it out

The hover event is written to the class name animation, so if the class name does not exist, the animation will not take effect

code

<template>
  <div class="wh-button-box">
    <div class="btn">
      <a :class="{animation:useAnimation}" href="#">{{title}}</a>
    </div>
  </div>
</template>

<script lang='ts'>
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'Wh-button'.props: {
    title: {
      type: String.default: 'button',},backgroundColor: {
      type: String.default: 'rgba (16, 143, 111, 0.3)',},useAnimation: {
      type: Boolean.default: false,}},setup() {
    return{}; }});</script>

<style lang='scss'>// Omitted part of the CSS code here, see the source code for details.wh-button-box {
  .btn{&:hover .animation::before {
      transform: skewX(45deg) translateX(200%);
    }
    &:hover .animation {
      background: v-bind('state.color'); }}}</style>
Copy the code

Component

Normal incoming attributes can be used, the use of double tags here is purely personal habit 😁😁

<wh-button @click="onConfirm"  title='confirm' :use-animation="true" :background-color="` rgba (160, 21, 114, 0.3) `"></wh-button>
<wh-button @click="onCancle" title='cancel' :use-animation="true"></wh-button>
Copy the code

Overlay

show

Look at the effect

attribute

  • Show Whether to enable the mask layer
  • ZIndex Specifies the z-axis coordinate of the mask layer
  • CloseOnClickModal whether the content can be closed by clicking on the mask

Design ideas/highlights

🌝 uses a new vue3 feature called teleport, which is used to insert the contents of the component into the specified node (in my case, the body)

🌝 has added a transition effect of 0.3 seconds to make it a little smoother. The transition class name in vue3 has changed a little bit. I didn’t notice it when I started writing it, so the transition effect didn’t take effect. V-leave has become V-leave-form, the overall usage is the same as before, please visit the vue3 website for more specific changes

The 🌝 mask layer is typically used in conjunction with other components, such as Dialog, so a property is set to configure whether the Dialog can be closed by clicking on the mask layer. This requires configuring closeOnClickModal to true and performing one of the parent component’s close methods

code

<template> <! -- Portal where the Dom node is inserted into the body --><teleport to='body'>
    <! Transition animations are smoother with a 0.3s fade in and out -->
    <transition name="base">
      <! -- Mask layer -->
      <div class="overly" :style="`z-index:${zIndex}`" v-if="show" @click='handleClick'></div>
    </transition>
  </teleport>
</template>

<script lang='ts'>
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'Overlay'.props: {
    show: {
      type: Boolean.default: false,},zIndex: {
      type: Number.default: 2000,},closeOnClickModal: {
      type: Boolean.default: false,}},emits: ['close'].setup(props, { emit }) {
    const handleClick = () = > {
      if (props.closeOnClickModal) {
        emit('close');
      }
      return;
    };
    return{ handleClick, }; }});</script>

<style lang='scss'>// Omit some CSS code here, see the source code for details // fade in and out.base-enter-active..base-leave-active {
  transition: all 0.3 s ease;
}
.base-enter-to..base-leave-from {
  opacity: 1;
}
.base-enter-from..base-leave-to {
  opacity: 0;
}
</style>
Copy the code

Component

Setting the close-on-click-modal to true and passing a close method, you can click on the mask layer to close other components (such as Dialog), which need to be manually set in the close method: the show binding property is set to false.

<overlay :show='dialogVisible' @close='onCancle' :close-on-click-modal='true'></overlay>
Copy the code

Dialog

show

First look at the effect, the yellow circle is the screen recording software, I did not write 🤦♂️🤦♂️🤦

attribute

  • DialogVisible Whether to display popovers
  • The title title
  • Width the width of the
  • Top The position from the top

Design ideas/highlights

🌝 is bound to the mask layer with the same value. You can close the mask layer when you close the popover, or you can pass a close method to the mask layer and close the popover by clicking on the mask layer

🌝 Use teleport to insert the popover to. Dialog(class=’Dialog’), why insert it here, just to practice encapsulating an hooks that generate nodes

// The hook function that creates the DOM node inserts a div node of the custom class into the body
// The setup function is executed as created, so there is no need to write the lifecycle
import { onUnmounted } from 'vue';
const useDOMCreate = (nodeId: string): HTMLDivElement= > {
  // Generate a div node
  const node = document.createElement('div');
  // Assign a class
  node.className = nodeId;
  // Insert div node into body
  document.body.appendChild(node);
  // Remove the DOM node during component uninstallation
  onUnmounted(() = > {
    document.body.removeChild(node);
  });
  return node;
};
export default useDOMCreate;
Copy the code

🌝 uses transition to add a fade in and out transition with a 20px shift for a better look

The 🌝 component itself is divided into three parts. The header is a title, and the body is a plug, which can be added through the parent component.

The footer section is two buttons that perform the parent component’s confirmation and cancellation callbacks.

code

<template> <! -- Mask layer --><overlay :show='dialogVisible' @close='onCancle' :close-on-click-modal='true'></overlay><! -- Portal Where the Dom node will be inserted. In the Dialog -- -- ><teleport to='.Dialog'>
    <div class="dialog-box" v-bind='$attrs'>
      <! Transition animation has a 0.3s fade in and out and a 20px move to make it smoother -->
      <transition name="dialog">
        <div class="dialog-content" v-if='dialogVisible' :style="`width:${width}; margin-top:${top}`">
          <! -- Dialog component header section -->
          <div class="dialog-header">
            <h5 class="dialog-title">{{title}}</h5>
          </div>
          <! -- Dialog component content slot -->
          <slot>
          </slot>
          <! -- Dialog component footer -->
          <div class="dialog-footer">
            <wh-button @click="onConfirm" class="footer-btn" title='confirm' :use-animation="true" :background-color="` rgba (160, 21, 114, 0.3) `"></wh-button>
            <wh-button @click="onCancle" class="footer-btn" title='cancel' :use-animation="true"></wh-button>
          </div>
        </div>
      </transition>
    </div>
  </teleport>
</template>

<script lang='ts'>
// Omit the file reference here, see the source code for details
export default defineComponent({
  components: {
    Overlay,
    WhButton,
  },
  name: 'Dialog'.props: {
    dialogVisible: {
      type: Boolean.default: false,},title: {
      type: String.default: 'tip',},width: {
      type: String.default: '400px',},top: {
      type: String.default: '15vh',}},emits: ['cancle'.'confirm'].setup(props, { emit }) {
    // Create the dom node.dialog
    useDOMCreate('Dialog');
    / / close the Dialog
    const onCancle = () = > {
      emit('cancle');
    };
    const onConfirm = () = > {
      emit('confirm');
    };
    return{ onCancle, onConfirm, }; }});</script>

<style lang='scss'>// Omitted part of the CSS code here, see the source code for details.dialog-enter-active..dialog-leave-active {
  transition: all 0.3 s ease;
}
.dialog-enter-to..dialog-leave-from {
  opacity: 1;
}
.dialog-enter-from..dialog-leave-to {
  transform: translateY(-20px);
  opacity: 0;
}
</style>
Copy the code

Component

The form component is inserted as the content. Click here for another of my articles on the Form component

👉👉 Bookmark – Basic Components (Form,Input)

<! -- Add a single bookmark popover --><Dialog :dialog-visible='isShowAddMarkDialog' @cancle='onCancle' @confirm='onConfirm' title='Add bookmark'>
  <wh-from ref='form'>
    </wh-from>
</Dialog>
Copy the code

Message

show

I’m just showing you the default, but there are other colors

attribute

  • Id identifies
  • Type type
  • The message content
  • Duration Indicates the time when the delay disappears
  • OffsetNumber offset

🌝 uses Transition to add transition effects, visual effects to pull up, fade in, fade out, and move a body position, using the type attribute passed in to bind the class to achieve different color effects

🌝 can be used by passing in a configuration object, or a string, which will eventually be converted to an object

🌝 uses fixed positioning. The offset from the top is calculated by the number of messages. All messages are stored in an array , you can calculate the offset for each message

🌝 adds a destruct method to the instance, triggers this method during the transition after-leave cycle, operates on the array to delete data, and changes the offsetNumber in the props to cause the view to refresh. See the code below and the source code.

code

//Message.vue<template> <! -- Transition effect fades in and out and moves one position --><transition name='message' @after-leave="$emit('destroy')">
    <div :class="classes" v-show='visiable' :style='styles' class="message">
      {{message}}
    </div>
  </transition>
</template>

<script lang='ts'>
// Omit the file reference here, see the source code for details
export default defineComponent({
  name: 'Message'.props: {
    id: {
      type: String.default: ' ',},type: {
      type: String as PropType<MessageType>,
      default: 'default',},message: {
      type: String.default: 'Here's a tip.',},duration: {
      type: Number.default: 2000,},offsetNumber: {
      type: Number.default: 0,}},setup(props) {
    const classes = computed(() = > ['message-' + props.type]);
    const visiable = ref(false);
    let timer: any = null;
    const startTimer = () = > {
      timer = setTimeout(() = > {
        visiable.value = false;
      }, props.duration);
    };
    const styles = computed(() = > {
      return { top: `${(props.offsetNumber - 1) * 55 + 20}px` };
    });
    // The component has rendered the display
    onMounted(() = > {
      visiable.value = true;
      // Start the timer
      startTimer();
    });
    onUnmounted(() = > {
      clearTimeout(timer);
    });
    return{ classes, visiable, styles, }; }});</script>

<style lang='scss'>// Omitted part of the CSS code here, see the source code for details.message-enter-active..message-leave-active {
  transition: all 0.3 s ease;
}
.message-enter-to..message-leave-from {
  opacity: 1;
}
.message-enter-from..message-leave-to {
  transform: translate(-50%, -100%);
  opacity: 0;
}
</style>
Copy the code
//index.ts
import Message from './Message.vue';
import { createVNode, render, VNode, reactive, computed } from 'vue';
/ / type
export type MessageType = 'success' | 'error' | 'default';
// The data of a message
exportinterface MessageOptions { id? : string; type? : MessageType; message? : string; duration? : number; offsetNumber? : number; }export type MessageParams = MessageOptions | string;
export type MessageList = MessageOptions[];

// Store all message instances to calculate offsets
const instances = reactive<VNode[]>([]);
const offsetNumber = computed(() = > instances.length + 1);
const createMessage = (options: MessageParams) = > {
  // If the argument is a string, convert it to an options object
  if (typeof options === 'string') {
    options = {
      message: options,
    };
  }
  // Create the component as a virtual node, i.e. an instance of the component
  constvnode = createVNode(Message, { ... options,offsetNumber: offsetNumber.value,
  });

  // Create a container
  const container = document.createElement('div');

  // Render the instance into the container
  render(vnode, container);
  // Put the rendered result on the body
  // Insert the first child because there will be one more div
  // Why don't you just put it in the body, create a container and fetch the contents
  document.body.appendChild(container.firstElementChild!) ; instances.push(vnode);// Add a destruction method to the instancevnode.props! .onDestroy =() = > {
    instances.shift();
    instances.forEach((item: any) = > {
      item.component.props.offsetNumber -= 1;
    });
    / / remove the dom
    render(null, container);
  };
};

export default createMessage;
Copy the code

Component

For a functional call, pass either a configuration object or a string

// Omit the file reference here
createMessage({ type: 'success'.message: 'Tag added successfully! ' });
Copy the code

Write in the last

Please feel free to comment below or send me a personal message. Thanks very much 🙏🙏🙏.

✅ thinks that some part of my design is too tedious, there is a simpler or more demanding way of packaging

✅ thinks some of my code is too old and can provide a new API or the latest syntax

✅ does not understand part of the article

✅ answers some of the questions in my article

✅ thinks certain interactions, features need to be optimized and bugs found

✅ wants to add new features and has better suggestions for the overall design, look and feel

Finally, thanks for your patience. Now that you’re here, click 👍 to like before you go

Links to integrate

🔊 Project preview address (GitHub Pages):👉👉alanhzw.github

🔊 Project preview alternate address (own server):👉👉warbler.duwanyu.com

🔊 source address (gitee):👉👉gitee.com/hzw_0174/Wa…

🔊 source address (github):👉👉github.com/alanhzw/War…

🔊 Overall project introduction :👉👉juejin.cn/post/696394…

🔊 streamer bookmark – Build a Vite+Vue3+Ts project from zero :👉👉juejin.cn/post/695130…

🔊 bookmark – Basic components (Form,Input):👉👉juejin.cn/post/696393…

🔊 streetwalkers bookmarks – based component (Button, Overlay, Dialog, Message) : 👉 👉 juejin. Cn/post / 696394…

🔊 Streamer bookmark – Service component introduction :👉👉 Currently unavailable

🔊 My blog :👉👉www.duwanyu.com