├── .all-contributorsrc
├── .babelrc
├── .browserslistrc
├── .commitlintrc.js
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitauthor.js
├── .gitignore
├── .huskyrc.js
├── .npmignore
├── .npmrc
├── .prettierrc.js
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README-cn.md
├── README.md
├── example
├── .eslintignore
├── .gitignore
├── README.md
├── babel.config.js
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
└── src
│ ├── App.vue
│ ├── assets
│ └── logo.png
│ └── main.js
├── package.json
├── rollup.config.js
├── src
├── index.css
├── index.js
└── util.js
└── test
└── test.md
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "js-npm-template",
3 | "projectOwner": "Rabbitzzc",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 10,
10 | "commit": true,
11 | "commitConvention": "gitmoji",
12 | "contributors": [],
13 | "contributorsPerLine": 7
14 | }
15 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env" // 根据浏览器和运行环境,自动的确定 babel 插件和 polyfills
4 | ],
5 | "plugins": [
6 | "@babel/plugin-proposal-class-properties"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 |
2 | # Browsers that we support
3 |
4 | last 1 version
5 | > 1%
6 | maintained node versions
7 | not dead
--------------------------------------------------------------------------------
/.commitlintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional'],
3 | // rules 中的内容可自定义,当前与 @commitlint/config-conventional 一致
4 | // 用于查看可自定义项
5 | rules: {
6 | // body 以空行开头
7 | 'body-leading-blank': [1, 'always'],
8 | // footer 以空行开头
9 | 'footer-leading-blank': [1, 'always'],
10 | // header 最大长度 72 个字符
11 | 'header-max-length': [2, 'always', 72],
12 | // 提交影响范围
13 | 'scope-case': [1, 'always', 'lower-case'],
14 | // 提交内容
15 | 'subject-case': [2, 'always', 'lower-case'],
16 | // 提交内容不能为空
17 | 'subject-empty': [2, 'never'],
18 | // 提交内容不能以 . 结尾
19 | 'subject-full-stop': [2, 'never', '.'],
20 | // 提交类型案例
21 | 'type-case': [2, 'always', 'lower-case'],
22 | // 提交类型不能为空
23 | 'type-empty': [2, 'never'],
24 | // 支持的提交类型
25 | 'type-enum': [
26 | 2,
27 | 'always',
28 | [
29 | 'build',
30 | 'chore',
31 | 'ci',
32 | 'docs',
33 | 'feat',
34 | 'fix',
35 | 'perf',
36 | 'refactor',
37 | 'revert',
38 | 'style',
39 | 'test'
40 | ]
41 | ]
42 | }
43 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | # 表明这是最顶层的配置文件,这样才会停止继续向上查找 .editorconfig 文件;
4 | # 查找的 .editorconfig 文件是从顶层开始读取的,类似变量作用域的效果,内部
5 | # 的 .editorconfig 文件属性优先级更高
6 | root = true
7 |
8 | # 指定作用文件格式 - 表示任意文件 ,比如js文件 py文件 php 文件等
9 | [*]
10 |
11 | # 缩进的类型 [space | tab]
12 | indent_style = space
13 |
14 | # 缩进的数量,我喜欢4
15 | indent_size = 4
16 |
17 | # 定义换行符 [lf | cr | crlf]
18 | end_of_line = lf
19 |
20 | # 编码格式。支持latin1、utf-8、utf-8-bom、utf-16be和utf-16le,不建议使用uft-8-bom
21 | charset = utf-8
22 |
23 | # 是否去除换行首尾的任意空白字符
24 | trim_trailing_whitespace = true
25 |
26 | # 文件最后是否使用一个空白行结尾
27 | insert_final_newline = true
28 |
29 | [*.md]
30 | trim_trailing_whitespace = false
31 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | test/*
4 |
5 | dist/*
6 |
7 | build/*
8 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: 'babel-eslint',
3 | extends: [
4 | 'alloy',
5 | ],
6 | parserOptions: {
7 | ecmaVersion: 2017,
8 | sourceType: 'module',
9 | ecmaFeatures: {
10 | experimentalObjectRestSpread: true,
11 | jsx: true
12 | }
13 | },
14 | env: {
15 | browser: true,
16 | node: true,
17 | commonjs: true,
18 | es6: true
19 | },
20 | root: true,
21 | rules: {
22 | 'for-direction': 'error',
23 | 'getter-return': [
24 | 'error',
25 | {
26 | allowImplicit: false
27 | }
28 | ],
29 | 'no-await-in-loop': 'off',
30 | 'no-compare-neg-zero': 'error',
31 | 'no-cond-assign': [
32 | 'error',
33 | 'except-parens'
34 | ],
35 | 'no-console': 'off',
36 | 'no-constant-condition': [
37 | 'error',
38 | {
39 | checkLoops: false
40 | }
41 | ],
42 | 'no-control-regex': 'error',
43 | 'no-debugger': 'error',
44 | 'no-dupe-args': 'error',
45 | 'no-dupe-keys': 'error',
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/.gitauthor.js:
--------------------------------------------------------------------------------
1 | const exec = require('child_process').exec
2 |
3 | exec('git config --get user.name && git config --get user.email', (error, stdout, stderr) => {
4 | if (stdout) {
5 | console.log('\x1b[36m%s\x1b[0m', 'start checking user!(∩_∩)O~\n')
6 | const arr = stdout.split('\n')
7 | const user = {
8 | name: arr[0],
9 | email: arr[1]
10 | }
11 | console.log('user.name: ', user.name)
12 | console.log('user.email: ', user.email)
13 |
14 | if (compareAuthor(user)) {
15 | console.log('\x1b[32m%s\x1b[0m', 'check pass~')
16 | process.exit(0)
17 | } else {
18 | console.log('\x1b[31m%s\x1b[0m', 'check fail~')
19 | process.exit(1)
20 | }
21 |
22 | } else {
23 | console.log('no git info!')
24 | }
25 | })
26 | // git author 信息
27 | const authors = [{
28 | name: 'Rabbitzzc',
29 | email: 'zzclovelcs@gmail.com'
30 | }]
31 |
32 |
33 | function compareAuthor(author) {
34 | return authors.some(v => v.name === author.name && v.email === author.email)
35 | }
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | .DS_Store
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 | *.pid.lock
19 |
20 |
21 | # lock
22 | yarn.lock
23 | package-lock.json
24 |
25 | # Directory for instrumented libs generated by jscoverage/JSCover
26 | lib-cov
27 |
28 | # Coverage directory used by tools like istanbul
29 | coverage
30 | *.lcov
31 |
32 | # nyc test coverage
33 | .nyc_output
34 |
35 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
36 | .grunt
37 |
38 | # Bower dependency directory (https://bower.io/)
39 | bower_components
40 |
41 | # node-waf configuration
42 | .lock-wscript
43 |
44 | # Compiled binary addons (https://nodejs.org/api/addons.html)
45 | build/Release
46 |
47 | # Dependency directories
48 | node_modules/
49 | jspm_packages/
50 |
51 | # TypeScript v1 declaration files
52 | typings/
53 |
54 | # TypeScript cache
55 | *.tsbuildinfo
56 |
57 | # Optional npm cache directory
58 | .npm
59 |
60 | # Optional eslint cache
61 | .eslintcache
62 |
63 | # Microbundle cache
64 | .rpt2_cache/
65 | .rts2_cache_cjs/
66 | .rts2_cache_es/
67 | .rts2_cache_umd/
68 |
69 | # Optional REPL history
70 | .node_repl_history
71 |
72 | # Output of 'npm pack'
73 | *.tgz
74 |
75 | # Yarn Integrity file
76 | .yarn-integrity
77 |
78 | # dotenv environment variables file
79 | .env
80 | .env.test
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 |
85 | # Next.js build output
86 | .next
87 |
88 | # Nuxt.js build / generate output
89 | .nuxt
90 | dist
91 |
92 | # Gatsby files
93 | .cache/
94 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
95 | # https://nextjs.org/blog/next-9-1#public-directory-support
96 | # public
97 |
98 | # vuepress build output
99 | .vuepress/dist
100 |
101 | # Serverless directories
102 | .serverless/
103 |
104 | # FuseBox cache
105 | .fusebox/
106 |
107 | # DynamoDB Local files
108 | .dynamodb/
109 |
110 | # TernJS port file
111 | .tern-port
112 |
--------------------------------------------------------------------------------
/.huskyrc.js:
--------------------------------------------------------------------------------
1 | const tasks = arr => arr.join(' && ')
2 |
3 | module.exports = {
4 | 'hooks': {
5 | 'pre-commit': tasks([
6 | 'yarn commit-name',
7 | 'yarn lint',
8 | 'yarn test',
9 | ]),
10 | 'commit-msg': tasks([
11 | 'yarn commit-msg'
12 | ]),
13 | 'pre-push': tasks([
14 | 'yarn pre-push'
15 | ])
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # 推荐使用 pkg.files 来过滤文件,和 npmignore 最大的差异就是使用白名单。npmignore 文件通过配置一个黑名单列表,当 npm publish 时这些文件不被发布。但是当新增配置文件或某些工具生成临时文件时,这个黑名单就会收到影响
2 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | # npm
2 | # registry=https://registry.npmjs.org
3 | # cnpm 🇨🇳
4 | registry=https://registry.npmjs.org
5 | # save-exact=true
6 | # engine-strict=true
7 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 120,
3 | tabWidth: 4,
4 | useTabs: false,
5 | singleQuote: true,
6 | semi: false,
7 | trailingComma: "none",
8 | bracketSpacing: true,
9 | arrowParens: 'always',
10 | htmlWhitespaceSensitivity: 'ignore'
11 | }
12 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js # 使用 nodejs
2 | node_js: # 版本为 8
3 | - "10"
4 |
5 | cache: yarn # 使用 yarn 而不是 npm (按照实际情况调整)
6 | install: yarn
7 |
8 | script:
9 | - yarn test
10 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rabbitzzc/step-tooltip/4654a7e92c568c30e97de9bb50223e90c1fa7cd5/CHANGELOG.md
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rabbitzzc/step-tooltip/4654a7e92c568c30e97de9bb50223e90c1fa7cd5/CONTRIBUTING.md
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 step-tooltip
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README-cn.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rabbitzzc/step-tooltip/4654a7e92c568c30e97de9bb50223e90c1fa7cd5/README-cn.md
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Step Tooltip
4 |
step by step tooltip guilder for any website... lightweight & minify✨✨✨.
5 |

6 |

7 |

8 |

9 |

10 |

11 |

12 |

13 |
14 |
15 |
16 |
17 | ## ⭐️ Features
18 |
19 | - Lightweight, minify.
20 | - Generate arrows from pseudo-classes(before/after).
21 |
22 |
23 | 🤚🎨🤚🎨demo: https://step-tooltip-example.logiczc.vercel.app/
24 |
25 |
26 | ## 📦 Getting Started
27 |
28 | ### install
29 | ```sh
30 | # npm
31 | npm install --save step-tooltip
32 |
33 | # yarn
34 | yarn add step-tooltip
35 | ```
36 |
37 | ### usage
38 |
39 | ```sh
40 | import stepTooltip from 'step-tooltip'
41 |
42 | stepTooltip()
43 | ```
44 |
45 | ### config
46 |
47 | ```js
48 | {
49 | initialText: '哈喽,准备好了解 step-tooltip 了么?',
50 | steps: [],
51 | backdropColor: 'rgb(0 0 0 / 0.56)',
52 | options: {
53 | backLabel: '上一步',
54 | nextLabel: '下一步',
55 | skipLabel: '我知道了',
56 | doneLabel: '完成',
57 | }
58 | }
59 | ```
60 |
61 | | config | func | default |
62 | | ------------- | --------------------------------- | ------------------------------------------------------------ |
63 | | initialText | first show text | `哈喽,准备好了解 step-tooltip 了么?` |
64 | | steps | every step | `[]` |
65 | | backdropColor | backdrop element box-shadow color | `'rgb(0 0 0 / 0.56)'` |
66 | | options | other config | 1. backLabel: 上一步
2. nextLabel: 下一步
3. skipLabel: 我知道了
4. doneLabel: 完成 |
67 |
68 |
69 |
70 | ## 🔖 LICENSE
71 |
72 | [MIT](./LICENSE)
73 |
74 | ## ✈️ Thanks
75 | * [cssarrowplease](http://www.cssarrowplease.com/), css arrow.
76 |
--------------------------------------------------------------------------------
/example/.eslintignore:
--------------------------------------------------------------------------------
1 | src
2 | dist
3 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
25 | .vercel
26 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # example
2 |
3 | ## Project setup
4 | ```
5 | yarn install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | yarn serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | yarn build
16 | ```
17 |
18 | ### Lints and fixes files
19 | ```
20 | yarn lint
21 | ```
22 |
23 | ### Customize configuration
24 | See [Configuration Reference](https://cli.vuejs.org/config/).
25 |
--------------------------------------------------------------------------------
/example/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "core-js": "^3.6.5",
12 | "step-tooltip": "1.0.5",
13 | "vue": "^2.6.11"
14 | },
15 | "devDependencies": {
16 | "@vue/cli-plugin-babel": "~4.5.0",
17 | "@vue/cli-plugin-eslint": "~4.5.0",
18 | "@vue/cli-service": "~4.5.0",
19 | "babel-eslint": "^10.1.0",
20 | "eslint": "^6.7.2",
21 | "eslint-plugin-vue": "^6.2.2",
22 | "vue-template-compiler": "^2.6.11"
23 | },
24 | "eslintConfig": {
25 | "root": true,
26 | "env": {
27 | "node": true
28 | },
29 | "extends": [
30 | "plugin:vue/essential",
31 | "eslint:recommended"
32 | ],
33 | "parserOptions": {
34 | "parser": "babel-eslint"
35 | },
36 | "rules": {}
37 | },
38 | "browserslist": [
39 | "> 1%",
40 | "last 2 versions",
41 | "not dead"
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rabbitzzc/step-tooltip/4654a7e92c568c30e97de9bb50223e90c1fa7cd5/example/public/favicon.ico
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/example/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
1
5 |
2
6 |
3
7 |
4
8 |
5
9 |
6
10 |
7
11 |
8
12 |
13 |
14 |
15 |
16 |
73 |
74 |
171 |
--------------------------------------------------------------------------------
/example/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rabbitzzc/step-tooltip/4654a7e92c568c30e97de9bb50223e90c1fa7cd5/example/src/assets/logo.png
--------------------------------------------------------------------------------
/example/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 |
4 | Vue.config.productionTip = false
5 |
6 | new Vue({
7 | render: h => h(App),
8 | }).$mount('#app')
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "step-tooltip",
3 | "version": "1.0.5",
4 | "description": "step by step tooltip guilder for any website.lightweight, & minify✨✨✨.",
5 | "main": "dist/index.min.js",
6 | "module": "dist/index.min.js",
7 | "directories": {
8 | "test": "test"
9 | },
10 | "scripts": {
11 | "build": "rollup -c",
12 | "prettier": "prettier --write src/*",
13 | "test": "echo \"Error: no test specified\"",
14 | "lint": "eslint --fix",
15 | "commit-msg": "commitlint -e $GIT_PARAMS",
16 | "commit-name": "node .gitauthor.js",
17 | "pre-push": "npx branch-name-lint",
18 | "contributors:add": "all-contributors add",
19 | "contributors:generate": "all-contributors generate"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/npm-template/js-npm-template.git"
24 | },
25 | "keywords": [
26 | "js-npm-template",
27 | "npm-template",
28 | "js"
29 | ],
30 | "author": "Rabbitzzc",
31 | "license": "MIT",
32 | "bugs": {
33 | "url": "https://github.com/npm-template/js-npm-template/issues"
34 | },
35 | "homepage": "https://github.com/npm-template/js-npm-template#readme",
36 | "engines": {
37 | "node": ">=10.0.0"
38 | },
39 | "files": [
40 | "dist"
41 | ],
42 | "devDependencies": {
43 | "@babel/core": "^7.11.6",
44 | "@babel/plugin-proposal-class-properties": "7.10.4",
45 | "@babel/plugin-transform-runtime": "^7.11.5",
46 | "@babel/preset-env": "^7.11.5",
47 | "@commitlint/cli": "11.0.0",
48 | "@commitlint/config-conventional": "11.0.0",
49 | "@rollup/plugin-babel": "5.2.1",
50 | "all-contributors-cli": "6.17.2",
51 | "autoprefixer": "^9.8.6",
52 | "babel-eslint": "^10.1.0",
53 | "branch-name-lint": "^1.4.0",
54 | "eslint": "7.9.0",
55 | "eslint-config-alloy": "^3.8.0",
56 | "husky": "^4.2.5",
57 | "prettier": "2.1.1",
58 | "rollup": "^2.26.11",
59 | "rollup-plugin-clear": "2.0.7",
60 | "rollup-plugin-postcss": "3.1.8",
61 | "rollup-plugin-terser": "^7.0.2"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import {babel} from "@rollup/plugin-babel"
2 |
3 | import postcss from 'rollup-plugin-postcss'
4 | import autoPreFixer from 'autoprefixer'
5 |
6 | // 压缩
7 | import {
8 | terser
9 | } from "rollup-plugin-terser"
10 |
11 | // 输出文件夹清除
12 | import clear from "rollup-plugin-clear"
13 |
14 |
15 | const path = require('path')
16 | const resolve = function (filePath) {
17 | return path.join(__dirname, filePath)
18 | }
19 |
20 |
21 | export default {
22 | input: resolve('src/index.js'), // 入口文件
23 | output: [{ // 出口文件
24 | file: resolve('dist/index.min.js'),
25 | format: 'umd',
26 | name: 'index',
27 | }],
28 | plugins: [
29 | clear({
30 | targets: ["dist"]
31 | }),
32 | postcss({
33 | extensions: ['.css'],
34 | minimize: true, // 压缩
35 | extract: resolve('dist/index.min.css'),
36 | plugins: [autoPreFixer()]
37 | }),
38 | babel({
39 | exclude: 'node_modules/**',
40 | // babelHelpers: 'runtime'
41 | }),
42 | terser(),
43 | ],
44 | external: []
45 | }
46 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | .step-tooltip-backdrop {
2 | position: fixed;
3 | top: 0px;
4 | bottom: 0px;
5 | left: 0px;
6 | right: 0px;
7 | box-sizing: content-box;
8 | z-index: 999999;
9 | background: rgba(0, 0, 0, 0.8);
10 | }
11 |
12 | .step-tooltip-active-matte {
13 | position: absolute;
14 | transition-duration: 150ms;
15 | transition-property: transform;
16 | will-change: transform;
17 | transition-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1);
18 | }
19 |
20 | .step-tooltip-active-container {
21 | position: absolute;
22 | z-index: 999998;
23 | box-sizing: border-box;
24 | word-break: break-all;
25 | background: #f9f9f9;
26 | max-width: 500px;
27 | min-width: 100px;
28 | color: #000;
29 | padding: 24px;
30 | border-radius: 5px;
31 | -webkit-box-shadow: 0 5px 30px rgba(0, 0, 0, 0.1);
32 | box-shadow: 0 5px 30px rgba(0, 0, 0, 0.1);
33 |
34 | transition-property: transform;
35 | will-change: transform;
36 | transform: translate(0, 0);
37 | transition: transform 0.15s ease-out;
38 | }
39 |
40 | #step-tooltip-active-container-text {
41 | margin-bottom: 20px;
42 | }
43 |
44 | .step-tooltip-next,
45 | .step-tooltip-back,
46 | .step-tooltip-end {
47 | padding: 4px 10px;
48 | font-size: 12px;
49 | color: #2d2d2d;
50 | background: #fff;
51 | border: 1px solid #dadada;
52 | border-radius: 10%;
53 | cursor: pointer;
54 | outline: none;
55 | box-sizing: border-box;
56 | margin: 0 2px;
57 | }
58 |
59 | .step-tooltip-footer {
60 | display: flex;
61 | justify-content: space-between;
62 | }
63 |
64 | .scroll-disabled {
65 | overflow: hidden;
66 | -webkit-overflow-scrolling: hidden;
67 | }
68 |
69 | .step-tooltip-disabled-btn[disabled] {
70 | cursor: not-allowed;
71 | background: #dadada;
72 | color: #f6f6f6;
73 | }
74 |
75 | /* arrow */
76 | .step-tooltip-arrow-bottom :after,
77 | .step-tooltip-arrow-top :after,
78 | .step-tooltip-arrow-left :after,
79 | .step-tooltip-arrow-right :after {
80 | border: solid transparent;
81 | content: ' ';
82 | height: 0;
83 | width: 0;
84 | position: absolute;
85 | pointer-events: none;
86 | border-width: 10px;
87 | }
88 |
89 | .step-tooltip-arrow-bottom :after {
90 | bottom: 100%;
91 | border-bottom-color: #fff;
92 | margin-left: -10px;
93 | }
94 | .step-tooltip-arrow-top :after {
95 | top: 100%;
96 | border-top-color: #fff;
97 | margin-left: -10px;
98 | }
99 | .step-tooltip-arrow-left :after {
100 | left: 100%;
101 | border-left-color: #fff;
102 | margin-top: -10px;
103 | }
104 | .step-tooltip-arrow-right :after {
105 | right: 100%;
106 | border-right-color: #fff;
107 | margin-top: -10px;
108 | }
109 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | getElement,
3 | calculatePositions,
4 | calculateArrowPosition,
5 | OFFSET
6 | } from './util.js'
7 |
8 | import './index.css'
9 | class StepTip {
10 | // 默认定位
11 | static defaultPlacement = 'bottom'
12 | // 整体的背景墙
13 | static backdropHTML = ''
14 | // 底部按钮 Done Back Next
15 | constructor(options) {
16 | // 配置项
17 | this.options = {...{
18 | initialText: '哈喽,准备好了解 step-tooltip 了么?',
19 | steps: [], // {element, container}
20 | backdropColor: 'rgb(0 0 0 / 0.56)',
21 | options: {
22 | backLabel: '上一步',
23 | nextLabel: '下一步',
24 | skipLabel: '我知道了',
25 | doneLabel: '完成',
26 | }
27 | },...options}
28 |
29 | this.startStep()
30 | }
31 |
32 | setListener() {
33 | getElement('#step-tooltip-back').addEventListener('click', () => this.toggleStep(-1))
34 | getElement('#step-tooltip-next').addEventListener('click', () => this.toggleStep(1))
35 | getElement('#step-tooltip-end').addEventListener('click', () => this.endStep())
36 | }
37 |
38 | removeListener() {
39 | getElement('#step-tooltip-back').removeEventListener('click', () => {})
40 | getElement('#step-tooltip-next').removeEventListener('click', () => {})
41 | getElement('#step-tooltip-end').removeEventListener('click', () => {})
42 | }
43 |
44 | startStep() {
45 | // 当前 stepIndex
46 | this.stepIndex = 0
47 |
48 | getElement("body").innerHTML += StepTip.backdropHTML
49 | this.backdrop = getElement('#step-tooltip-backdrop')
50 | this.selfStyle = document.head.appendChild(document.createElement("style"))
51 |
52 | this.setup()
53 | this.setListener()
54 | }
55 | // 初始化工作
56 | setup() {
57 | const {
58 | steps
59 | } = this.options
60 |
61 | if (!steps || !steps.length) return
62 |
63 | const current = steps[this.stepIndex]
64 |
65 | const {
66 | element,
67 | container,
68 | } = current
69 |
70 | let placement = current.hasOwnProperty('placement') ? current.placement : StepTip.defaultPlacement
71 |
72 | const innerWidth = window.innerWidth
73 | // 对于宽度很小的场景,应该考虑展示在下方或者上面
74 | if (innerWidth <= 400 && (placement === 'left' || placement === 'right')) placement = StepTip.defaultPlacement
75 |
76 | const elm = getElement(element)
77 |
78 | // 错误
79 | if (!elm) throw new Error('Cant\'t find' + element + ', please check for spelling mistakes.')
80 |
81 | // 禁止 body 滚动
82 | getElement('body').classList.add('scroll-disabled')
83 |
84 | // 让元素滚动到可视区域内 scrollIntoViewIfNeeded 兼容性不行
85 | elm.scrollIntoView({
86 | behavior: 'smooth',
87 | block: 'center'
88 | })
89 |
90 | const elmRect = elm.getBoundingClientRect()
91 |
92 | // 生成一个DOM元素
93 | const activeElm = this.createActiveMatte(elmRect, getComputedStyle(elm))
94 | const containerElm = this.createContainer(container)
95 |
96 | // 创建 arrow css
97 | const placements = ['top', 'left', 'bottom', 'right']
98 | placements.forEach(v => containerElm.classList.remove(`step-tooltip-arrow-${v}`))
99 | containerElm.removeAttribute('data-top')
100 | containerElm.removeAttribute('data-left')
101 |
102 | containerElm.classList.add(`step-tooltip-arrow-${placement}`)
103 |
104 | let position = calculatePositions(elm, containerElm, placement)
105 |
106 | let {
107 | width
108 | } = containerElm.getBoundingClientRect()
109 |
110 |
111 | // 是否在可视区域
112 | if (position.x + width >= innerWidth) {
113 | // 向左移
114 | position.x = Math.round(elmRect.right - width + OFFSET)
115 | } else if (position.x <= 0) {
116 | // 向右移
117 | position.x = Math.round(elmRect.x - OFFSET)
118 |
119 | // 如果宽度小于浏览器宽度,则需要调整宽度
120 | if (width >= innerWidth) {
121 | containerElm.style.width = (innerWidth - position.x * 2) + 'px'
122 | }
123 | }
124 |
125 | const arrowStyle = calculateArrowPosition(activeElm, position, placement)
126 | document.head.removeChild(this.selfStyle)
127 | this.selfStyle = document.head.appendChild(document.createElement("style"))
128 | if (placement === 'left' || placement === 'right') {
129 | this.selfStyle.innerHTML = `.step-tooltip-arrow-left :after {top:${arrowStyle.top}} .step-tooltip-arrow-right :after {top:${arrowStyle.top}}`
130 | } else {
131 | this.selfStyle.innerHTML = `.step-tooltip-arrow-bottom :after {left:${arrowStyle.left}} .step-tooltip-arrow-top :after {left:${arrowStyle.left}}`
132 | }
133 |
134 | // 有了 DOM ,也有了偏移量
135 | containerElm.style.transform = `translate(${position.x}px,${position.y}px)`
136 |
137 | }
138 |
139 | // 切换前后
140 | toggleStep(move) {
141 | const {
142 | steps
143 | } = this.options
144 |
145 | this.stepIndex += move
146 |
147 | if (this.stepIndex >= 0 && this.stepIndex < steps.length) {
148 | return this.setup()
149 | } else {
150 | this.endStep()
151 | }
152 | }
153 |
154 | // 终止整个流程
155 | endStep() {
156 | getElement('body').classList.remove('scroll-disabled')
157 | document.head.removeChild(this.selfStyle)
158 |
159 | this.removeListener()
160 |
161 | this.backdrop.parentNode.removeChild(this.backdrop)
162 | this.backdrop = null
163 |
164 | this.stepIndex = 0
165 |
166 | // TODO 调用 onComplete 事件
167 | }
168 |
169 | // 创建 active 蒙版 div
170 | createActiveMatte(elmRect,styles) {
171 | const { backdropColor } = this.options
172 | let activeElm = getElement("#step-tooltip-backdrop .step-tooltip-active-matte")
173 | if (!activeElm) {
174 | activeElm = document.createElement("div")
175 | activeElm.setAttribute("id", "step-tooltip-active-matte")
176 | activeElm.classList.add("step-tooltip-active-matte")
177 | this.backdrop.append(activeElm)
178 | }
179 | activeElm.style.top = Math.round(elmRect.top) + "px"
180 | activeElm.style.left = Math.round(elmRect.left) + "px"
181 | activeElm.style.height = elmRect.height + "px"
182 | activeElm.style.width = elmRect.width + "px"
183 | activeElm.style.borderRadius = styles.borderRadius
184 | activeElm.style.boxShadow = "0 0 0 9999px " + backdropColor
185 | return activeElm
186 | }
187 |
188 | // 创建一个容器,添加配置信息
189 | createContainer(container) {
190 | const {
191 | steps
192 | } = this.options
193 |
194 | let containerElm = getElement('#step-tooltip-backdrop .step-tooltip-active-container')
195 |
196 | // 如果不存在则需要重新创建
197 | if (!containerElm) {
198 | containerElm = document.createElement('div')
199 |
200 | // 分层,提高动画性能
201 | containerElm.style.willChange = 'transform'
202 | containerElm.classList.add('step-tooltip-active-container')
203 | // 自定义内容
204 | containerElm.innerHTML += ""
205 | // 自定义footer
206 | containerElm.innerHTML += ``
207 |
208 | this.backdrop.append(containerElm)
209 | }
210 |
211 | // button 内置样式
212 | const backBtn = getElement('#step-tooltip-back')
213 | const nextBtn = getElement('#step-tooltip-next')
214 |
215 | // 第一次的时候,禁用 pre
216 | if (this.stepIndex === 0) {
217 | backBtn.setAttribute('disabled', true)
218 | backBtn.classList.add('step-tooltip-disabled-btn')
219 | } else {
220 | backBtn.removeAttribute('disabled', true)
221 | backBtn.classList.remove('step-tooltip-disabled-btn')
222 | }
223 |
224 | nextBtn.innerText = this.stepIndex === steps.length - 1 ? this.options.options.doneLabel : this.options.options.nextLabel
225 |
226 | // 内容区域展示配置信息
227 | getElement('#step-tooltip-active-container-text').innerHTML = container
228 |
229 | return containerElm
230 | }
231 | }
232 |
233 | // 返回一个函数,返回实例
234 | export default options => new StepTip(options)
235 |
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | // 常量
2 | export const OFFSET = 15
3 |
4 | // 简化 selector
5 | export const getElement = (selector) => document.querySelector(selector)
6 |
7 | // 定位
8 | export const calculatePositions = (el, desc, placement = 'bottom') => {
9 | let position
10 | const elRect = el.getBoundingClientRect()
11 | const descRect = desc.getBoundingClientRect()
12 |
13 | const c = placement[0]
14 | if (c === 't' || c === 'b') {
15 | position = {
16 | x: Math.round(elRect.x + (elRect.width - descRect.width)/2),
17 | y: c === 't' ?
18 | Math.round(elRect.y - descRect.height - OFFSET) : Math.round(elRect.y + elRect.height + OFFSET)
19 | }
20 | } else {
21 | position = {
22 | x: c === 'l' ?
23 | Math.round(elRect.x - descRect.width - OFFSET) : Math.round(elRect.x + elRect.width + OFFSET),
24 | y: Math.round(elRect.y + (elRect.height - descRect.height) / 2)
25 | }
26 | }
27 |
28 | return position
29 | }
30 |
31 |
32 | export const calculateArrowPosition = (el, position, placement='bottom') => {
33 | const c = placement[0]
34 | let style = {}
35 |
36 | const elRect = el.getBoundingClientRect()
37 | if(c === 't' || c === 'b') {
38 | style.left = Math.round(elRect.width/2 + elRect.x - position.x) + 'px'
39 | if(c === 't') {
40 | style.top = '100%'
41 | } else {
42 | style.bottom = '100%'
43 | }
44 |
45 | } else {
46 | style.top = Math.round(elRect.height/2 + elRect.y - position.y) + 'px'
47 | if(c === 'l') {
48 | style.left = '100%'
49 | } else {
50 | style.right = '100%'
51 | }
52 | }
53 | return style
54 | }
55 |
--------------------------------------------------------------------------------
/test/test.md:
--------------------------------------------------------------------------------
1 | > 该文件夹为测试目录,用存储编写测试文件,主要是单元测试。
2 |
3 | #### 单元测试(`Unit Test`)
4 | 以function 为最小单位,验证在特定情况下的input 和output 是否正确。
5 |
6 |
7 | ##### 为什么需要测试
8 | http://www.hollischuang.com/archives/954
9 |
10 | #### 测试工具
11 | - [mocha](https://mochajs.cn/)
12 | - [chaijs](https://www.chaijs.com/)
13 | - [node-tap](https://node-tap.org/)
--------------------------------------------------------------------------------