👉👉项目预览地址,能够直接设置为浏览器主页或者桌面快捷方式进行使用javascript
彻底开源,你们能够随意研究,二次开发。固然仍是十分欢迎你们点个Star⭐⭐⭐
👉👉源码连接(gitee) 👉👉源码连接(github)css
本文记录了流莺书签封装的部分基础组件,包括vue
⭐ Buttonjava
⭐ Overlaynode
⭐ Dialogreact
⭐ Messagegit
因为本项目是为了练手,因此在某些组件中可能也添加了一些实际并无用到的功能,接下来将逐个介绍这些组件的属性,方法,以及一些设计思路.github
除了一些特殊的样式,文章不会大量贴出CSS
代码,因为我SASS
功底不是很好,CSS
代码看起来也是比较臃肿😣😣😣,感兴趣的同窗能够自行查看源码数组
基本就是一个组件的.vue
文件和一个对应的index.ts
,index.ts
存放一些基础数据,类型声明等浏览器
├── src
├── assets // 存放静态资源
├── baseComponents // 基础组件
└──Button // 按钮组件
│ └──Button.vue
└── Message // 弹窗组件
├──Message.vue
└──index.ts
......
复制代码
先来看效果,能够看到就是鼠标移入移出的时候会有一个动画
🌝按钮的背景颜色使用了vue3
新增特性,直接在css
中绑定了props
的变量backgroundColor
,不了解新特性的小伙伴能够前往官网查看哟
🌝经过动态绑定class
来启用动画,由于css
中是给类名animation
写的hover
事件,因此若是类名不存在了,动画天然不生效了
<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: '按钮', }, backgroundColor: { type: String, default: 'rgba(16, 143, 111, 0.3)', }, useAnimation: { type: Boolean, default: false, }, }, setup() { return {}; }, }); </script>
<style lang='scss'> //此处省略部分CSS代码,详情请查看源码 .wh-button-box { .btn { &:hover .animation::before { transform: skewX(45deg) translateX(200%); } &:hover .animation { background: v-bind('state.color'); } } } </style>
复制代码
正常传入属性使用便可,这里使用双标签纯属我的习惯😁😁
<wh-button @click="onConfirm" title='确认' :use-animation="true" :background-color="`rgba(160, 21, 114, 0.3)`"></wh-button>
<wh-button @click="onCancle" title='取消' :use-animation="true"></wh-button>
复制代码
先看效果
🌝使用了vue3
的新特性teleport
,这个组件的做用是把里面的内容插入到指定的节点当中,我这里是插入在body
里了
🌝添加了0.3秒的一个过渡效果,这样显得平滑一点,这里须要注意的是vue3
中的transition
类名发生了一些小变化,我刚开始写的时候没注意到,结果过渡效果就没生效,查了半天才发现v-enter
变成了v-enter-form
, v-leave
变成了v-leave-form
,总体的使用方式仍是跟之前同样,更多具体的变更请移步vue3官网
🌝遮罩层通常都结合其余组件使用,好比Dialog
,因此这里设置了一个属性,来配置是否能够经过点击遮罩层来关闭Dialog
,须要配置closeOnClickModal
为true
,并执行父组件的一个close
方法
<template>
<!-- 传送门 此处Dom节点会被插在body里 -->
<teleport to='body'> <!-- 过渡动画 添加了0.3s的淡入淡出 显得更加平滑 --> <transition name="base"> <!-- 遮罩层 --> <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'> //此处省略部分CSS代码,详情请查看源码 //淡入淡出 .base-enter-active, .base-leave-active { transition: all 0.3s ease; } .base-enter-to, .base-leave-from { opacity: 1; } .base-enter-from, .base-leave-to { opacity: 0; } </style>
复制代码
配置close-on-click-modal
为true
并传入一个close
方法,就能够点击遮罩层关闭其余组件了(如Dialog
),须要在close
方法中手动的设置:show
绑定的属性为false
.
<overlay :show='dialogVisible' @close='onCancle' :close-on-click-modal='true'></overlay>
复制代码
先看效果,点击的那个黄色圆圈是录屏软件自带的,不是我写的🤦♂️🤦♂️🤦♂️
🌝和遮罩层绑定同一个值,能够在关闭弹窗的同时关闭遮罩层,也能够给遮罩层传递一个close
方法,经过点击遮罩层关闭弹窗
🌝使用teleport
将弹窗插入到.Dialog(class='Dialog')
中,至于为何要插入到这里,只是为了练习封装一个用来生成节点的hooks
//建立DOM节点的hook函数 在body中插入一个自定义class的div节点
//setup函数在执行时等同于created 因此不必写入生命周期
import { onUnmounted } from 'vue';
const useDOMCreate = (nodeId: string): HTMLDivElement => {
// 生成一个div的节点
const node = document.createElement('div');
// 给赋值一个class
node.className = nodeId;
// 在body中插入div节点
document.body.appendChild(node);
// 在组件卸载的时候移除dom节点
onUnmounted(() => {
document.body.removeChild(node);
});
return node;
};
export default useDOMCreate;
复制代码
🌝使用transition
添加了一个淡入淡出的过渡,而且有20px的位移,视觉效果更好
🌝组件自己分为三个部分,header
就是一个标题,body
部分是一个插糟,能够经过父组件添加一些内容进来,
footer
部分是两个button
,执行父组件的确认和取消回调.
<template>
<!-- 遮罩层 -->
<overlay :show='dialogVisible' @close='onCancle' :close-on-click-modal='true'></overlay>
<!-- 传送门 此处Dom节点会被插在.Dialog里 -->
<teleport to='.Dialog'> <div class="dialog-box" v-bind='$attrs'> <!-- 过渡动画 添加了0.3s的淡入淡出 而且有20px的移动 显得更加平滑 --> <transition name="dialog"> <div class="dialog-content" v-if='dialogVisible' :style="`width:${width};margin-top:${top}`"> <!-- Dialog组件header部分 --> <div class="dialog-header"> <h5 class="dialog-title">{{title}}</h5> </div> <!-- Dialog组件内容插槽 --> <slot> </slot> <!-- Dialog组件footer部分 --> <div class="dialog-footer"> <wh-button @click="onConfirm" class="footer-btn" title='确认' :use-animation="true" :background-color="`rgba(160, 21, 114, 0.3)`"></wh-button> <wh-button @click="onCancle" class="footer-btn" title='取消' :use-animation="true"></wh-button> </div> </div> </transition> </div> </teleport>
</template>
<script lang='ts'> //此处省略文件引用,详情请查看源码 export default defineComponent({ components: { Overlay, WhButton, }, name: 'Dialog', props: { dialogVisible: { type: Boolean, default: false, }, title: { type: String, default: '提示', }, width: { type: String, default: '400px', }, top: { type: String, default: '15vh', }, }, emits: ['cancle', 'confirm'], setup(props, { emit }) { // 建立dom节点 .Dialog useDOMCreate('Dialog'); // 关闭Dialog const onCancle = () => { emit('cancle'); }; const onConfirm = () => { emit('confirm'); }; return { onCancle, onConfirm, }; }, }); </script>
<style lang='scss'> //此处省略部分CSS代码,详情请查看源码 .dialog-enter-active, .dialog-leave-active { transition: all 0.3s ease; } .dialog-enter-to, .dialog-leave-from { opacity: 1; } .dialog-enter-from, .dialog-leave-to { transform: translateY(-20px); opacity: 0; } </style>
复制代码
正常传入属性使用便可,form
组件做为内容插入,有关form组件能够点击这里查看个人另外一篇文章
<!-- 添加单个书签的弹窗 -->
<Dialog :dialog-visible='isShowAddMarkDialog' @cancle='onCancle' @confirm='onConfirm' title='添加书签'> <wh-from ref='form'> </wh-from> </Dialog>
复制代码
先看效果,这里只展现了默认的状况,其实还有其余颜色的
设计思路/亮点 🌝使用transition
添加了过渡效果,视觉效果拉满,淡入淡出,而且移动一个身位,经过传入的类型属性来绑定class
达成不一样颜色的效果
🌝使用的时候能够传入一个配置对象,或者一个字符串,若是是字符串最终仍是会转换为对象
🌝使用fixed定位
,距离顶部的偏移量经过message
的数量来计算,全部的message
存放在一个数组中,新增和移除一条message
的时候就相应的操做数组,再根据 数组的长度 * 一个固定的高度 ,就能够算出每一条message
的偏移量
🌝给实例添加一个销毁的方法,在transition
的after-leave
周期中触发这个方法,在这个方法中操做数组删除数据,并修改props
中的offsetNumber
来形成视图的刷新.具体的查看下方代码以及源码.
//Message.vue
<template>
<!-- 过渡效果 淡入淡出 而且移动一个身位 -->
<transition name='message' @after-leave="$emit('destroy')"> <div :class="classes" v-show='visiable' :style='styles' class="message"> {{message}} </div> </transition>
</template>
<script lang='ts'> //此处省略文件引用,详情请查看源码 export default defineComponent({ name: 'Message', props: { id: { type: String, default: '', }, type: { type: String as PropType<MessageType>, default: 'default', }, message: { type: String, default: '这是一条提示', }, 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` }; }); // 组件渲染完了显示 onMounted(() => { visiable.value = true; // 开启定时器 startTimer(); }); onUnmounted(() => { clearTimeout(timer); }); return { classes, visiable, styles, }; }, }); </script> <style lang='scss'> //此处省略部分CSS代码,详情请查看源码 .message-enter-active, .message-leave-active { transition: all 0.3s ease; } .message-enter-to, .message-leave-from { opacity: 1; } .message-enter-from, .message-leave-to { transform: translate(-50%,-100%); opacity: 0; } </style> 复制代码
//index.ts
import Message from './Message.vue';
import { createVNode, render, VNode, reactive, computed } from 'vue';
// 类型
export type MessageType = 'success' | 'error' | 'default';
//一条消息的数据
export interface MessageOptions {
id?: string;
type?: MessageType;
message?: string;
duration?: number;
offsetNumber?: number;
}
export type MessageParams = MessageOptions | string;
export type MessageList = MessageOptions[];
// 存放全部的message实例 用来计算偏移量
const instances = reactive<VNode[]>([]);
const offsetNumber = computed(() => instances.length + 1);
const createMessage = (options: MessageParams) => {
// 若是参数是string类型,就把它转换为options对象
if (typeof options === 'string') {
options = {
message: options,
};
}
// 把组件建立为虚拟节点 也就是一个组件实例
const vnode = createVNode(Message, {
...options,
offsetNumber: offsetNumber.value,
});
// 建立一个容器
const container = document.createElement('div');
// 把实例渲染到容器里
render(vnode, container);
// 将渲染后的结果 放到body上
// 由于会多一个div 因此插入第一个孩子
// 为何不直接放到body里,要建立一个容器再取里面的内容呢 为了销毁组件 若是直接放在body里,就清空全部节点了
document.body.appendChild(container.firstElementChild!);
instances.push(vnode);
// 给实例添加一个销毁方法
vnode.props!.onDestroy = () => {
instances.shift();
instances.forEach((item: any) => {
item.component.props.offsetNumber -= 1;
});
// 移除dom
render(null, container);
};
};
export default createMessage;
复制代码
函数式调用,传入配置对象,或者传入一个字符串便可
//此处省略文件引用
createMessage({ type: 'success', message: '添加标签成功!' });
复制代码
请你们不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.
✅认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式
✅认为我部分代码过于老旧,能够提供新的API或最新语法
✅对于文章中部份内容不理解
✅解答我文章中一些疑问
✅认为某些交互,功能须要优化,发现BUG
✅想要添加新功能,对于总体的设计,外观有更好的建议
最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧
🔊项目预览地址(GitHub Pages):👉👉alanhzw.github.io
🔊项目预览备用地址(本身的服务器):👉👉warbler.duwanyu.com
🔊源码地址(gitee):👉👉gitee.com/hzw_0174/Wa…
🔊源码地址(github):👉👉github.com/alanhzw/War…
🔊项目总体介绍:👉👉juejin.cn/post/696394…
🔊流莺书签-从零搭建一个Vite+Vue3+Ts项目:👉👉juejin.cn/post/695130…
🔊流莺书签-基础组件介绍(Form,Input):👉👉juejin.cn/post/696393…
🔊流莺书签-基础组件(Button,Overlay,Dialog,Message):👉👉juejin.cn/post/696394…
🔊流莺书签-业务组件介绍:👉👉暂无
🔊个人博客:👉👉www.duwanyu.com