初始化

Vite 基于原生 ES 模块提供了丰富的内建功能,开箱即用。同时,插件足够简单,它不需要任何运行时依赖,只需要安装 vite (用于开发与构建)和 sass (用于开发环境编译 .scss 文件)。

npm i -D vite scss

项目配置

同时用 vite 开发插件和构建插件 demo,所以我创建了两个 vite 配置文件。 在项目根目录创建 config 文件夹,存放 vite 配置文件。

插件配置

config/vite.config.ts 插件配置文件

import { defineConfig } from 'vite'
import { resolve } from 'path' export default defineConfig({
server: {
open: true,
port: 8080
},
build: {
emptyOutDir: true,
lib: {
formats: ['es', 'umd', 'iife'],
entry: resolve(__dirname, '../src/main.ts'),
name: 'EmojiPopover'
}
}
})

server 对象下存放开发时配置。自动打开浏览器,端口号设为 8080。

build 中存放构建时配置。build.emptyOutDir 是指打包时先清空上一次构建生成的目录。如果这是 webpack,你通常还需要安装 clean-webpack-plugin,并在 webpack 中进行一系列套娃配置才能实现这个简单的功能,或者手动添加删除命令在构建之前。而在 vite 中,仅需一句 emptyOutDir: true

通过 build.lib 开启 vite 库模式。vite 默认将 /index.html 作为入口文件,这通常应用在构建应用时。而构建一个库通常将 js/ts 作为入口,这在 vite 中同样容易实现,lib.entry 即可指定 入口为 src/main.ts 文件,这类似于 webpackConfig.entry。

再通过 lib.formats 指定构建后的文件格式以及通过 lib.name 指定文件导出的变量名称为 EmojiPopover。

插件示例配置

给插件写一个用于展示使用的网页,通常将它托管到 Pages 服务。直接通过 vite 本地开发和构建该插件的示例网页,同样容易实现。

config/vite.config.exm.ts 插件示例配置文件

import { defineConfig, loadEnv } from 'vite'
import { resolve } from 'path' export default ({ mode }) => {
const __DEV__ = mode === 'development' return defineConfig({
base: __DEV__ ? '/' : 'emoji-popover',
root: 'example',
server: {
open: false,
port: 3000
},
build: {
outDir: '../docs',
emptyOutDir: true
}
})
}

vite 配置文件还可以以上面这种形式存在,默认导出一个箭头函数,函数中再返回 defineConfig,这样我们可以通过解构直接取得一个参数 mode,通过它来区分当前是开发环境还是生产环境。

config.base 是指开发或生产环境服务的公共基础路径。因为我们需要将示例页面部署到 Pages 服务,生产环境修改 base 以保证能够正确加载资源。

构建后的示例网页 html 资源加载路径:

config.root 设置为 'example',因为我将示例页面资源放到 /example 目录下

通常构建后的目录为 dist, 这里 build.outDir 设为 'docs',原因是 Github Pages 默认只可以部署整个分支或者部署指定的 docs 目录。即将 example 构建输出到到 docs 并部署到 Pages 服务。

命令配置

我们还需要在 package.json 的 sript 字段中添加本地开发以及构建的命令,通过 --config <config path> 指定配置文件路径,因为我将 vite 配置文件都放到了 /config 下。

"scripts": {
"dev": "vite --config config/vite.config.ts",
"build": "vite build --config config/vite.config.ts",
"dev:exm": "vite --config config/vite.config.exm.ts",
"build:exm": "vite build --config config/vite.config.exm.ts"
},
  • dev 启动插件开发环境
  • build 构建插件
  • dev:exm 启动示例开发环境
  • build:exm 构建示例页面

编写插件

├─src
│ ├─utils
│ │ ├─types.ts
│ │ └─helpers.ts
│ ├─index.scss
│ └─main.ts

main.ts

import { isUrl } from './utils/helper'
import { IEmojiItem, IOptions } from './utils/types'
import './index.scss' class EmojiPopover {
private options: IOptions
private wrapClassName: string
private wrapCount: number
private wrapCountClassName: string constructor(private opts: IOptions) {
const defaultOptions: IOptions = {
container: 'body',
button: '.e-btn',
targetElement: '.e-input',
emojiList: [],
wrapClassName: '',
wrapAnimationClassName: 'anim-scale-in'
} this.options = Object.assign({}, defaultOptions, opts)
this.wrapClassName = 'emoji-wrap'
this.wrapCount = document.querySelectorAll('.emoji-wrap').length + 1
this.wrapCountClassName = `emoji-wrap-${this.wrapCount}` this.init()
this.createButtonListener()
} /**
* 初始化
*/
private init(): void {
const { emojiList, container, button, targetElement } = this.options const _emojiContainer = this.createEmojiContainer()
const _emojiList = this.createEmojiList(emojiList)
const _mask = this.createMask()
_emojiContainer.appendChild(_emojiList)
_emojiContainer.appendChild(_mask) const _targetElement = document.querySelector<HTMLElement>(targetElement)
const { left, top, height } = _targetElement.getClientRects()[0]
_emojiContainer.style.top = `${top + height + 12}px`
_emojiContainer.style.left = `${left}px` const _container: HTMLElement = document.querySelector(container)
_container.appendChild(_emojiContainer)
} /**
* 创建按钮事件
*/
private createButtonListener(): void {
const { button } = this.options
const _button = document.querySelector<HTMLElement>(button)
_button.addEventListener('click', () => this.toggle(true))
} /**
* 创建表情面板容器
* @returns {HTMLDivElement}
*/
private createEmojiContainer(): HTMLDivElement {
const { wrapAnimationClassName, wrapClassName } = this.options
const container: HTMLDivElement = document.createElement('div')
container.classList.add(this.wrapClassName)
container.classList.add(this.wrapCountClassName)
container.classList.add(wrapAnimationClassName)
if (wrapClassName !== '') {
container.classList.add(wrapClassName)
}
return container
} /**
* 创建表情列表面板
* @param {IEmojiItem} emojiList
* @returns {HTMLDivElement}
*/
private createEmojiList(emojiList: Array<IEmojiItem>) {
const emojiWrap: HTMLDivElement = document.createElement('div')
emojiWrap.classList.add('emoji-list') emojiList.forEach(item => {
const emojiItem = this.createEmojiItem(item)
emojiWrap.appendChild(emojiItem)
}) return emojiWrap
} /**
* 创建表情项
* @param {IEmojiItem} itemData
* @returns {HTMLDivElement}
*/
private createEmojiItem(emojiItemData): HTMLDivElement {
const { value, label } = emojiItemData
const emojiContainer: HTMLDivElement = document.createElement('div')
let emoji: HTMLImageElement | HTMLSpanElement if (isUrl(value)) {
emoji = document.createElement('img')
emoji.classList.add('emoji')
emoji.classList.add('emoji-img')
emoji.setAttribute('src', value)
} else {
emoji = document.createElement('span')
emoji.classList.add('emoji')
emoji.classList.add('emoji-text')
emoji.innerText = value
} emojiContainer.classList.add('emoji-item')
emojiContainer.appendChild(emoji) if (typeof label === 'string') {
emojiContainer.setAttribute('title', label)
} return emojiContainer
} /**
* 创建表情面板蒙层
* @returns {HTMLDivElement}
*/
private createMask(): HTMLDivElement {
const mask: HTMLDivElement = document.createElement('div')
mask.classList.add('emoji-mask')
mask.addEventListener('click', () => this.toggle(false))
return mask
} /**
* 打开或关闭表情面板
* @param isShow {boolean}
*/
public toggle(isShow: boolean) {
const emojiWrap: HTMLElement = document.querySelector(
`.${this.wrapCountClassName}`
)
emojiWrap.style.display = isShow ? 'block' : 'none'
} /**
* 选择表情
*/
public onSelect(callback) {
const emojiItems = document.querySelectorAll(
`.${this.wrapCountClassName} .emoji-item`
)
const _this = this emojiItems.forEach(function (item) {
item.addEventListener('click', function (e: Event) {
const currentTarget = e.currentTarget as HTMLElement
let value if (currentTarget.children[0].classList.contains('emoji-img')) {
value = currentTarget.children[0].getAttribute('src')
} else {
value = currentTarget.innerText
}
_this.toggle(false)
callback(value)
})
})
}
} export default EmojiPopover

编写 d.ts

使用 rollup 构建库时,通常借助 rollup 插件自动生成 d.ts 文件。但是尝试了社区的两个 vite dts 插件,效果不尽人意。由于这个项目比较简单,干脆直接手写一个 d.ts 文件。在 public 下创建 d.ts 文件,vite 会在构建时自动将 /public 中的资源拷贝到 dist 目录下。

public/emoji-popover.d.ts

export interface IEmojiItem {
value: string
label?: string
} export interface IOptions {
button: string
container?: string
targetElement: string
emojiList: Array<IEmojiItem>
wrapClassName?: string
wrapAnimationClassName?: string
} export declare class EmojiButton {
private options: IOptions
private wrapClassName: string
private wrapCount: number
private wrapCountClassName: string constructor(options: IOptions) private init(): void
private createButtonListener(): void
private createEmojiContainer()
private createEmojiList()
private createEmojiItem()
private createMask()
/*
* Toggle emoji popover.
*/
public toggle(isShow: boolean): void
/*
* Listen to Choose an emoji.
*/
public onSelect(callback: (value: string) => void): void
} export default EmojiButton

构建生成的文件结构如下:

├─dist
│ ├─emoji-popover.d.ts
│ ├─emoji-popover.es.js
│ ├─emoji-popover.iife.js
│ ├─emoji-popover.umd.js
│ └─style.css

插件样式

有了 CSS 自定义属性(或称为 “CSS 变量”),可以不借助 css 预处理器即可实现样式的定制,且是运行时的。也就是说,可以通过 CSS 自定义属性实现插件的样式定制甚至网页深色模式的跟随,本博客评论框中的 emoji 就是基于这个插件,它可以跟随本博客的深色模式。

:root {
--e-color-border: #e1e1e1; /* EmojiPopover border color */
--e-color-emoji-text: #666; /* text emoji font color */
--e-color-border-emoji-hover: #e1e1e1; /* emoji hover border color */
--e-color-bg: #fff; /* EmojiPopover background color */
--e-bg-emoji-hover: #f8f8f8; /* emoji hover background color */
--e-size-emoji-text: 16px; /* text emoji font size */
--e-width-emoji-img: 20px; /* image emoji width */
--e-height-emoji-img: 20px; /* image emoji height */
--e-max-width: 288px; /* EmojiPopover max width */
} .emoji-wrap {
display: none;
position: absolute;
padding: 8px;
max-width: var(--e-max-width);
background-color: var(--e-color-bg);
border: 1px solid var(--e-color-border);
border-radius: 4px;
z-index: 3;
&::before,
&::after {
position: absolute;
content: '';
margin: 0;
width: 0;
height: 0;
}
&:after {
top: -9px;
left: 14px;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-bottom: 8px solid var(--e-color-border);
}
&::before {
top: -8px;
left: 14px;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-bottom: 8px solid var(--e-color-bg);
z-index: 1;
}
} .emoji-list {
display: flex;
flex-wrap: wrap;
} .emoji-item {
display: flex;
justify-content: center;
align-items: center;
padding: 6px 6px;
color: var(--e-color-emoji-text);
cursor: pointer;
box-sizing: border-box;
border: 1px solid transparent;
border-radius: 4px;
user-select: none;
&:hover {
background: var(--e-bg-emoji-hover);
border-color: var(--e-color-border-emoji-hover);
& > .emoji-text {
transform: scale(1.2);
transition: transform 0.15s cubic-bezier(0.2, 0, 0.13, 2);
}
}
} .emoji-text {
font-size: var(--e-size-emoji-text);
font-weight: 500;
line-height: 1.2em;
white-space: nowrap;
} .emoji-img {
width: var(--e-width-emoji-img);
height: var(--e-height-emoji-img);
} .emoji-mask {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2;
display: block;
cursor: default;
content: ' ';
background: transparent;
z-index: -1;
} .anim-scale-in {
animation-name: scale-in;
animation-duration: 0.15s;
animation-timing-function: cubic-bezier(0.2, 0, 0.13, 1.5);
} @keyframes scale-in {
0% {
opacity: 0;
transform: scale(0.5);
}
100% {
opacity: 1;
transform: scale(1);
}
}

全局插件样式

你可以重写这些 CSS 变量(CSS 自定义属性)来定制样式。

:root {
--e-color-border: #e1e1e1; /* EmojiPopover border color */
--e-color-emoji-text: #666; /* text emoji font color */
--e-color-border-emoji-hover: #e1e1e1; /* emoji hover border color */
--e-color-bg: #fff; /* EmojiPopover background color */
--e-bg-emoji-hover: #f8f8f8; /* emoji hover background color */
--e-size-emoji-text: 16px; /* text emoji font size */
--e-width-emoji-img: 20px; /* image emoji width */
--e-height-emoji-img: 20px; /* image emoji height */
--e-max-width: 288px; /* EmojiPopover max width */
}

指定实例样式

如果有多个实例,你可以通过 css 变量 scope 应用到指定实例。

.<custom-class-name> {
--e-color-border: #e1e1e1; /* EmojiPopover border color */
--e-color-emoji-text: #666; /* text emoji font color */
--e-color-border-emoji-hover: #e1e1e1; /* emoji hover border color */
--e-color-bg: #fff; /* EmojiPopover background color */
--e-bg-emoji-hover: #f8f8f8; /* emoji hover background color */
--e-size-emoji-text: 16px; /* text emoji font size */
--e-width-emoji-img: 20px; /* image emoji width */
--e-height-emoji-img: 20px; /* image emoji height */
--e-max-width: 288px; /* EmojiPopover max width */
}

使用你的 CSS

Emoji Popover 生成非常简单的 DOM 结构,你也可以使用自己的样式而不是导入 style.css

编写示例网页

├─example
│ ├─index.html
│ └─index.css

首先安装已经发布到 npm 的表情弹窗插件

npm i emoji-popover
example/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DEMO · emoji-popover</title>
</head>
<body>
<div class="container">
<div class="wrap">
<input class="e-input" type="text" />
<button class="e-btn">系统表情</button>
</div>
<div class="wrap">
<input class="e-input-2" type="text" />
<button class="e-btn-2">文本表情</button>
</div>
<div class="wrap">
<input class="e-input-3" type="text" />
<button class="e-btn-3">网络图片</button>
</div>
</div> <script type="module">
import EmojiPopover from 'emoji-popover'
import '../node_modules/emoji-popover/dist/style.css'
import './index.css' const e1 = new EmojiPopover({
button: '.e-btn',
container: 'body',
targetElement: '.e-input',
emojiList: [
{
value: '',
label: '笑哭'
},
{
value: '',
label: '笑哭'
},
{
value: '',
label: '大笑'
},
{
value: '',
label: '苦笑'
},
{
value: '',
label: '斜眼笑'
},
{
value: '',
label: '得意'
},
{
value: '',
label: '微笑'
},
{
value: '',
label: '酷!'
},
{
value: '',
label: '花痴'
},
{
value: '',
label: '呵呵'
},
{
value: '',
label: '好崇拜哦'
},
{
value: '',
label: '思考'
},
{
value: '',
label: '白眼'
},
{
value: '',
label: '略略略'
},
{
value: '',
label: '呆住'
},
{
value: '',
label: '大哭'
},
{
value: '',
label: '头炸了'
},
{
value: '',
label: '冷汗'
},
{
value: '',
label: '吓死了'
},
{
value: '',
label: '略略略'
},
{
value: '',
label: '晕'
},
{
value: '',
label: '愤怒'
},
{
value: '',
label: '祝贺'
},
{
value: '',
label: '小丑竟是我'
},
{
value: '',
label: '嘘~'
},
{
value: '',
label: '猴'
},
{
value: '',
label: '笑笑不说话'
},
{
value: '',
label: '牛'
},
{
value: '',
label: '啤酒'
}
]
}) e1.onSelect(value => {
document.querySelector('.e-input').value += value
}) const e2 = new EmojiPopover({
button: '.e-btn-2',
container: 'body',
targetElement: '.e-input-2',
emojiList: [
{
value: '(=・ω・=)',
label: ''
},
{
value: '(`・ω・´)',
label: ''
},
{
value: '(°∀°)ノ',
label: ''
},
{
value: '←_←',
label: ''
},
{
value: '→_→',
label: ''
},
{
value: 'Σ(゚д゚;)',
label: ''
},
{
value: '(。・ω・。)',
label: ''
},
{
value: '(-_-#)',
label: ''
}
]
}) e2.onSelect(value => {
document.querySelector('.e-input-2').value += value
}) const e3 = new EmojiPopover({
button: '.e-btn-3',
container: 'body',
targetElement: '.e-input-3',
emojiList: [
{
value:
'https://img1.baidu.com/it/u=3060109128,4247188337&fm=26&fmt=auto&gp=0.jpg',
label: ''
},
{
value:
'https://img2.baidu.com/it/u=358795348,3036825421&fm=26&fmt=auto&gp=0.jpg',
label: ''
}
]
}) e3.onSelect(value => {
document.querySelector('.e-input-3').value += value
})
</script>
</body>
</html>

构建后的示例目录如下,你也可以点击 这里 查看示例

链接

使用 vite 构建一个表情选择插件的更多相关文章

  1. 封装 oschina.net 表情选择

    1. [代码]jquery.facial.js //从OSCHINA.NET 提取出来的 表情选择 插件 by zhouxiang //如果有不满足的地方 可以自己改改 没事随便写写的 style 和 ...

  2. 移动设备日期选择插件(基于JQUERY)

    上周花了2个小时写的一个日期选择插件,比较适合移动端的设备.先看个效果图吧.如果刚好是你需要的就往下吧,不需要的也可以继续..... 其实网络上已经有的了类似的成熟插件,比如基于mobiscroll, ...

  3. 【jQuery插件】用jQuery Masonry快速构建一个pinterest网站布局(转)

    [jQuery插件]用jQuery Masonry快速构建一个pinterest网站布局 时间:2011年03月21日作者:愚人码头查看次数:29,744 views评论次数:25条评论 前段时间领导 ...

  4. 使用Gradle构建构建一个Java Web工程及持续集成环境Jenkins配置

    安装Eclipse插件——Buildship 什么是Buildship? Buildship能方便我们通过Eclipse IDE创建和导入Gradle工程,同时还能执行Gradle任务. Eclips ...

  5. [转]C/C++:构建你自己的插件框架

    本文译自Gigi Sayfan在DDJ上的专栏文章.Gigi Sayfan是北加州的一个程序员,email:gigi@gmail.com. 本文是一系列讨论架构.开发和部署C/C++跨平台插件框架的文 ...

  6. 利用 vue-cli 构建一个 Vue 项目

    一.项目初始构建 现在如果要构建一个 Vue 的项目,最方便的方式,莫过于使用官方的 vue-cli . 首先,咱们先来全局安装 vue-cli ,打开命令行工具,输入以下命令: $ npm inst ...

  7. (24/24) webpack小案例--自己动手用webpack构建一个React的开发环境

    通过前面的学习,对webpack有了更深的认识,故此节我们就利用前面相关知识自己动手用webpack构建一个React的开发环境,就算是一个小案例吧. 注:此处使用的开发工具是Webstorm. 1. ...

  8. 使用maven构建一个web项目

                          使用maven构建一个web项目     首先建立一个maven项目(关于环境配置上节有详细过程): 1)-->右键new,-->project ...

  9. [译]Spring Boot 构建一个RESTful Web服务

    翻译地址:https://spring.io/guides/gs/rest-service/ 构建一个RESTful Web服务 本指南将指导您完成使用spring创建一个“hello world”R ...

随机推荐

  1. Identity Server4 数据迁移、持久化

    add-migration InitialPersistedGrantDb -c PersistedGrantDbContext -o Migrations/IdentityServer/Persis ...

  2. 【Azure Redis 缓存】Azure Cache for Redis服务中,除开放端口6379,6380外,对13000,13001,15000,15001 为什么也是开放的呢?

    问题描述 在使用安全检测工具对Azure Redis服务端口进行扫描时,发现Redis对外开放了13001, 13000,15000,15001端口.非常不理解的是,在门户上只开放了6379,6380 ...

  3. 【敏杰开发】Beta阶段项目展示

    [敏杰开发]Beta阶段项目展示 项目相关地址汇总 线上地址:http://roadmap.imcoming.top 前端仓库:https://github.com/MinJieDev/Roadmap ...

  4. envoy 官方example运行失败问题处理

    镜像内安装包失败处理 方法一:修改Dockerfile,在Dockerfile中增加如下 ubuntu示例 RUN sed -i 's/archive.ubuntu.com/mirrors.aliyu ...

  5. [c++] 二级指针的原理

    示例 将值(实参)传递给值(形参),无法更改val 1 #include <iostream> 2 using namespace std; 3 4 void change(int mem ...

  6. WPS 2010 页眉下方添加下划线

    我们在使用Word2010编辑文档中时,有时需要在页眉下方删除或添加一条横线.本篇经验就来介绍一下删除和添加横线的方法. 工具/原料   Word 2010 一.删除横线   1 打开Word2010 ...

  7. Flex里的fx s mx

    笔记是从其他地方整合的,仅供参考 原来flex build 4有三个命名空间fx,mx,s,分别对应一下三个: •xmlns:fx="http://ns.adobe.com/mxml/200 ...

  8. 安装tomcat8 env

    目录 1 download package 2 install tomcat 3 Pro config 4. docker image 1 download package wget https:// ...

  9. 第13讲 | 套接字Socket:Talk is cheap, show me the code

    第13讲 | 套接字Socket:Talk is cheap, show me the code 基于 TCP 和 UDP 协议的 Socket 编程.在讲 TCP 和 UDP 协议的时候,我们分客户 ...

  10. 一文读懂一条 SQL 查询语句是如何执行的

    2001 年 MySQL 发布 3.23 版本,自此便开始获得广泛应用,随着不断地升级迭代,至今 MySQL 已经走过了 20 个年头. 为了充分发挥 MySQL 的性能并顺利地使用,就必须正确理解其 ...