经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » HTML/CSS » CSS » 查看文章
使用 vite 构建一个表情选择插件
来源:cnblogs  作者:guangzan  时间:2021/6/28 9:12:24  对本文有异议

初始化

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

  1. npm i -D vite scss

项目配置

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

插件配置

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

  1. import { defineConfig } from 'vite'
  2. import { resolve } from 'path'
  3. export default defineConfig({
  4. server: {
  5. open: true,
  6. port: 8080
  7. },
  8. build: {
  9. emptyOutDir: true,
  10. lib: {
  11. formats: ['es', 'umd', 'iife'],
  12. entry: resolve(__dirname, '../src/main.ts'),
  13. name: 'EmojiPopover'
  14. }
  15. }
  16. })

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 插件示例配置文件

  1. import { defineConfig, loadEnv } from 'vite'
  2. import { resolve } from 'path'
  3. export default ({ mode }) => {
  4. const __DEV__ = mode === 'development'
  5. return defineConfig({
  6. base: __DEV__ ? '/' : 'emoji-popover',
  7. root: 'example',
  8. server: {
  9. open: false,
  10. port: 3000
  11. },
  12. build: {
  13. outDir: '../docs',
  14. emptyOutDir: true
  15. }
  16. })
  17. }

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

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

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

image

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

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

image

命令配置

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

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

编写插件

  1. ├─src
  2. ├─utils
  3. ├─types.ts
  4. └─helpers.ts
  5. ├─index.scss
  6. └─main.ts

main.ts

  1. import { isUrl } from './utils/helper'
  2. import { IEmojiItem, IOptions } from './utils/types'
  3. import './index.scss'
  4. class EmojiPopover {
  5. private options: IOptions
  6. private wrapClassName: string
  7. private wrapCount: number
  8. private wrapCountClassName: string
  9. constructor(private opts: IOptions) {
  10. const defaultOptions: IOptions = {
  11. container: 'body',
  12. button: '.e-btn',
  13. targetElement: '.e-input',
  14. emojiList: [],
  15. wrapClassName: '',
  16. wrapAnimationClassName: 'anim-scale-in'
  17. }
  18. this.options = Object.assign({}, defaultOptions, opts)
  19. this.wrapClassName = 'emoji-wrap'
  20. this.wrapCount = document.querySelectorAll('.emoji-wrap').length + 1
  21. this.wrapCountClassName = `emoji-wrap-${this.wrapCount}`
  22. this.init()
  23. this.createButtonListener()
  24. }
  25. /**
  26. * 初始化
  27. */
  28. private init(): void {
  29. const { emojiList, container, button, targetElement } = this.options
  30. const _emojiContainer = this.createEmojiContainer()
  31. const _emojiList = this.createEmojiList(emojiList)
  32. const _mask = this.createMask()
  33. _emojiContainer.appendChild(_emojiList)
  34. _emojiContainer.appendChild(_mask)
  35. const _targetElement = document.querySelector<HTMLElement>(targetElement)
  36. const { left, top, height } = _targetElement.getClientRects()[0]
  37. _emojiContainer.style.top = `${top + height + 12}px`
  38. _emojiContainer.style.left = `${left}px`
  39. const _container: HTMLElement = document.querySelector(container)
  40. _container.appendChild(_emojiContainer)
  41. }
  42. /**
  43. * 创建按钮事件
  44. */
  45. private createButtonListener(): void {
  46. const { button } = this.options
  47. const _button = document.querySelector<HTMLElement>(button)
  48. _button.addEventListener('click', () => this.toggle(true))
  49. }
  50. /**
  51. * 创建表情面板容器
  52. * @returns {HTMLDivElement}
  53. */
  54. private createEmojiContainer(): HTMLDivElement {
  55. const { wrapAnimationClassName, wrapClassName } = this.options
  56. const container: HTMLDivElement = document.createElement('div')
  57. container.classList.add(this.wrapClassName)
  58. container.classList.add(this.wrapCountClassName)
  59. container.classList.add(wrapAnimationClassName)
  60. if (wrapClassName !== '') {
  61. container.classList.add(wrapClassName)
  62. }
  63. return container
  64. }
  65. /**
  66. * 创建表情列表面板
  67. * @param {IEmojiItem} emojiList
  68. * @returns {HTMLDivElement}
  69. */
  70. private createEmojiList(emojiList: Array<IEmojiItem>) {
  71. const emojiWrap: HTMLDivElement = document.createElement('div')
  72. emojiWrap.classList.add('emoji-list')
  73. emojiList.forEach(item => {
  74. const emojiItem = this.createEmojiItem(item)
  75. emojiWrap.appendChild(emojiItem)
  76. })
  77. return emojiWrap
  78. }
  79. /**
  80. * 创建表情项
  81. * @param {IEmojiItem} itemData
  82. * @returns {HTMLDivElement}
  83. */
  84. private createEmojiItem(emojiItemData): HTMLDivElement {
  85. const { value, label } = emojiItemData
  86. const emojiContainer: HTMLDivElement = document.createElement('div')
  87. let emoji: HTMLImageElement | HTMLSpanElement
  88. if (isUrl(value)) {
  89. emoji = document.createElement('img')
  90. emoji.classList.add('emoji')
  91. emoji.classList.add('emoji-img')
  92. emoji.setAttribute('src', value)
  93. } else {
  94. emoji = document.createElement('span')
  95. emoji.classList.add('emoji')
  96. emoji.classList.add('emoji-text')
  97. emoji.innerText = value
  98. }
  99. emojiContainer.classList.add('emoji-item')
  100. emojiContainer.appendChild(emoji)
  101. if (typeof label === 'string') {
  102. emojiContainer.setAttribute('title', label)
  103. }
  104. return emojiContainer
  105. }
  106. /**
  107. * 创建表情面板蒙层
  108. * @returns {HTMLDivElement}
  109. */
  110. private createMask(): HTMLDivElement {
  111. const mask: HTMLDivElement = document.createElement('div')
  112. mask.classList.add('emoji-mask')
  113. mask.addEventListener('click', () => this.toggle(false))
  114. return mask
  115. }
  116. /**
  117. * 打开或关闭表情面板
  118. * @param isShow {boolean}
  119. */
  120. public toggle(isShow: boolean) {
  121. const emojiWrap: HTMLElement = document.querySelector(
  122. `.${this.wrapCountClassName}`
  123. )
  124. emojiWrap.style.display = isShow ? 'block' : 'none'
  125. }
  126. /**
  127. * 选择表情
  128. */
  129. public onSelect(callback) {
  130. const emojiItems = document.querySelectorAll(
  131. `.${this.wrapCountClassName} .emoji-item`
  132. )
  133. const _this = this
  134. emojiItems.forEach(function (item) {
  135. item.addEventListener('click', function (e: Event) {
  136. const currentTarget = e.currentTarget as HTMLElement
  137. let value
  138. if (currentTarget.children[0].classList.contains('emoji-img')) {
  139. value = currentTarget.children[0].getAttribute('src')
  140. } else {
  141. value = currentTarget.innerText
  142. }
  143. _this.toggle(false)
  144. callback(value)
  145. })
  146. })
  147. }
  148. }
  149. export default EmojiPopover

编写 d.ts

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

public/emoji-popover.d.ts

  1. export interface IEmojiItem {
  2. value: string
  3. label?: string
  4. }
  5. export interface IOptions {
  6. button: string
  7. container?: string
  8. targetElement: string
  9. emojiList: Array<IEmojiItem>
  10. wrapClassName?: string
  11. wrapAnimationClassName?: string
  12. }
  13. export declare class EmojiButton {
  14. private options: IOptions
  15. private wrapClassName: string
  16. private wrapCount: number
  17. private wrapCountClassName: string
  18. constructor(options: IOptions)
  19. private init(): void
  20. private createButtonListener(): void
  21. private createEmojiContainer()
  22. private createEmojiList()
  23. private createEmojiItem()
  24. private createMask()
  25. /*
  26. * Toggle emoji popover.
  27. */
  28. public toggle(isShow: boolean): void
  29. /*
  30. * Listen to Choose an emoji.
  31. */
  32. public onSelect(callback: (value: string) => void): void
  33. }
  34. export default EmojiButton

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

  1. ├─dist
  2. ├─emoji-popover.d.ts
  3. ├─emoji-popover.es.js
  4. ├─emoji-popover.iife.js
  5. ├─emoji-popover.umd.js
  6. └─style.css

插件样式

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

  1. :root {
  2. --e-color-border: #e1e1e1; /* EmojiPopover border color */
  3. --e-color-emoji-text: #666; /* text emoji font color */
  4. --e-color-border-emoji-hover: #e1e1e1; /* emoji hover border color */
  5. --e-color-bg: #fff; /* EmojiPopover background color */
  6. --e-bg-emoji-hover: #f8f8f8; /* emoji hover background color */
  7. --e-size-emoji-text: 16px; /* text emoji font size */
  8. --e-width-emoji-img: 20px; /* image emoji width */
  9. --e-height-emoji-img: 20px; /* image emoji height */
  10. --e-max-width: 288px; /* EmojiPopover max width */
  11. }
  12. .emoji-wrap {
  13. display: none;
  14. position: absolute;
  15. padding: 8px;
  16. max-width: var(--e-max-width);
  17. background-color: var(--e-color-bg);
  18. border: 1px solid var(--e-color-border);
  19. border-radius: 4px;
  20. z-index: 3;
  21. &::before,
  22. &::after {
  23. position: absolute;
  24. content: '';
  25. margin: 0;
  26. width: 0;
  27. height: 0;
  28. }
  29. &:after {
  30. top: -9px;
  31. left: 14px;
  32. border-left: 8px solid transparent;
  33. border-right: 8px solid transparent;
  34. border-bottom: 8px solid var(--e-color-border);
  35. }
  36. &::before {
  37. top: -8px;
  38. left: 14px;
  39. border-left: 8px solid transparent;
  40. border-right: 8px solid transparent;
  41. border-bottom: 8px solid var(--e-color-bg);
  42. z-index: 1;
  43. }
  44. }
  45. .emoji-list {
  46. display: flex;
  47. flex-wrap: wrap;
  48. }
  49. .emoji-item {
  50. display: flex;
  51. justify-content: center;
  52. align-items: center;
  53. padding: 6px 6px;
  54. color: var(--e-color-emoji-text);
  55. cursor: pointer;
  56. box-sizing: border-box;
  57. border: 1px solid transparent;
  58. border-radius: 4px;
  59. user-select: none;
  60. &:hover {
  61. background: var(--e-bg-emoji-hover);
  62. border-color: var(--e-color-border-emoji-hover);
  63. & > .emoji-text {
  64. transform: scale(1.2);
  65. transition: transform 0.15s cubic-bezier(0.2, 0, 0.13, 2);
  66. }
  67. }
  68. }
  69. .emoji-text {
  70. font-size: var(--e-size-emoji-text);
  71. font-weight: 500;
  72. line-height: 1.2em;
  73. white-space: nowrap;
  74. }
  75. .emoji-img {
  76. width: var(--e-width-emoji-img);
  77. height: var(--e-height-emoji-img);
  78. }
  79. .emoji-mask {
  80. position: fixed;
  81. top: 0;
  82. right: 0;
  83. bottom: 0;
  84. left: 0;
  85. z-index: 2;
  86. display: block;
  87. cursor: default;
  88. content: ' ';
  89. background: transparent;
  90. z-index: -1;
  91. }
  92. .anim-scale-in {
  93. animation-name: scale-in;
  94. animation-duration: 0.15s;
  95. animation-timing-function: cubic-bezier(0.2, 0, 0.13, 1.5);
  96. }
  97. @keyframes scale-in {
  98. 0% {
  99. opacity: 0;
  100. transform: scale(0.5);
  101. }
  102. 100% {
  103. opacity: 1;
  104. transform: scale(1);
  105. }
  106. }

全局插件样式

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

  1. :root {
  2. --e-color-border: #e1e1e1; /* EmojiPopover border color */
  3. --e-color-emoji-text: #666; /* text emoji font color */
  4. --e-color-border-emoji-hover: #e1e1e1; /* emoji hover border color */
  5. --e-color-bg: #fff; /* EmojiPopover background color */
  6. --e-bg-emoji-hover: #f8f8f8; /* emoji hover background color */
  7. --e-size-emoji-text: 16px; /* text emoji font size */
  8. --e-width-emoji-img: 20px; /* image emoji width */
  9. --e-height-emoji-img: 20px; /* image emoji height */
  10. --e-max-width: 288px; /* EmojiPopover max width */
  11. }

指定实例样式

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

  1. .<custom-class-name> {
  2. --e-color-border: #e1e1e1; /* EmojiPopover border color */
  3. --e-color-emoji-text: #666; /* text emoji font color */
  4. --e-color-border-emoji-hover: #e1e1e1; /* emoji hover border color */
  5. --e-color-bg: #fff; /* EmojiPopover background color */
  6. --e-bg-emoji-hover: #f8f8f8; /* emoji hover background color */
  7. --e-size-emoji-text: 16px; /* text emoji font size */
  8. --e-width-emoji-img: 20px; /* image emoji width */
  9. --e-height-emoji-img: 20px; /* image emoji height */
  10. --e-max-width: 288px; /* EmojiPopover max width */
  11. }

使用你的 CSS

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

编写示例网页

  1. ├─example
  2. ├─index.html
  3. └─index.css

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

  1. npm i emoji-popover
example/index.html
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <title>DEMO · emoji-popover</title>
  7. </head>
  8. <body>
  9. <div class="container">
  10. <div class="wrap">
  11. <input class="e-input" type="text" />
  12. <button class="e-btn">系统表情</button>
  13. </div>
  14. <div class="wrap">
  15. <input class="e-input-2" type="text" />
  16. <button class="e-btn-2">文本表情</button>
  17. </div>
  18. <div class="wrap">
  19. <input class="e-input-3" type="text" />
  20. <button class="e-btn-3">网络图片</button>
  21. </div>
  22. </div>
  23. <script type="module">
  24. import EmojiPopover from 'emoji-popover'
  25. import '../node_modules/emoji-popover/dist/style.css'
  26. import './index.css'
  27. const e1 = new EmojiPopover({
  28. button: '.e-btn',
  29. container: 'body',
  30. targetElement: '.e-input',
  31. emojiList: [
  32. {
  33. value: '??',
  34. label: '笑哭'
  35. },
  36. {
  37. value: '??',
  38. label: '笑哭'
  39. },
  40. {
  41. value: '??',
  42. label: '大笑'
  43. },
  44. {
  45. value: '??',
  46. label: '苦笑'
  47. },
  48. {
  49. value: '??',
  50. label: '斜眼笑'
  51. },
  52. {
  53. value: '??',
  54. label: '得意'
  55. },
  56. {
  57. value: '??',
  58. label: '微笑'
  59. },
  60. {
  61. value: '??',
  62. label: '酷!'
  63. },
  64. {
  65. value: '??',
  66. label: '花痴'
  67. },
  68. {
  69. value: '??',
  70. label: '呵呵'
  71. },
  72. {
  73. value: '??',
  74. label: '好崇拜哦'
  75. },
  76. {
  77. value: '??',
  78. label: '思考'
  79. },
  80. {
  81. value: '??',
  82. label: '白眼'
  83. },
  84. {
  85. value: '??',
  86. label: '略略略'
  87. },
  88. {
  89. value: '??',
  90. label: '呆住'
  91. },
  92. {
  93. value: '??',
  94. label: '大哭'
  95. },
  96. {
  97. value: '??',
  98. label: '头炸了'
  99. },
  100. {
  101. value: '??',
  102. label: '冷汗'
  103. },
  104. {
  105. value: '??',
  106. label: '吓死了'
  107. },
  108. {
  109. value: '??',
  110. label: '略略略'
  111. },
  112. {
  113. value: '??',
  114. label: '晕'
  115. },
  116. {
  117. value: '??',
  118. label: '愤怒'
  119. },
  120. {
  121. value: '??',
  122. label: '祝贺'
  123. },
  124. {
  125. value: '??',
  126. label: '小丑竟是我'
  127. },
  128. {
  129. value: '??',
  130. label: '嘘~'
  131. },
  132. {
  133. value: '??',
  134. label: '猴'
  135. },
  136. {
  137. value: '??',
  138. label: '笑笑不说话'
  139. },
  140. {
  141. value: '??',
  142. label: '牛'
  143. },
  144. {
  145. value: '??',
  146. label: '啤酒'
  147. }
  148. ]
  149. })
  150. e1.onSelect(value => {
  151. document.querySelector('.e-input').value += value
  152. })
  153. const e2 = new EmojiPopover({
  154. button: '.e-btn-2',
  155. container: 'body',
  156. targetElement: '.e-input-2',
  157. emojiList: [
  158. {
  159. value: '(=?ω?=)',
  160. label: ''
  161. },
  162. {
  163. value: '(`?ω?′)',
  164. label: ''
  165. },
  166. {
  167. value: '(°?°)?',
  168. label: ''
  169. },
  170. {
  171. value: '←_←',
  172. label: ''
  173. },
  174. {
  175. value: '→_→',
  176. label: ''
  177. },
  178. {
  179. value: 'Σ(?д?;)',
  180. label: ''
  181. },
  182. {
  183. value: '(??ω??)',
  184. label: ''
  185. },
  186. {
  187. value: '(-_-#)',
  188. label: ''
  189. }
  190. ]
  191. })
  192. e2.onSelect(value => {
  193. document.querySelector('.e-input-2').value += value
  194. })
  195. const e3 = new EmojiPopover({
  196. button: '.e-btn-3',
  197. container: 'body',
  198. targetElement: '.e-input-3',
  199. emojiList: [
  200. {
  201. value:
  202. 'https://img1.baidu.com/it/u=3060109128,4247188337&fm=26&fmt=auto&gp=0.jpg',
  203. label: ''
  204. },
  205. {
  206. value:
  207. 'https://img2.baidu.com/it/u=358795348,3036825421&fm=26&fmt=auto&gp=0.jpg',
  208. label: ''
  209. }
  210. ]
  211. })
  212. e3.onSelect(value => {
  213. document.querySelector('.e-input-3').value += value
  214. })
  215. </script>
  216. </body>
  217. </html>

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

image

链接

原文链接:http://www.cnblogs.com/guangzan/p/14897223.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号