├── .babelrc ├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── README.zh.md ├── build ├── dev.js ├── rollup.config.prod.js └── webpack.config.dev.js ├── example ├── demo.gif ├── example.js ├── index.css ├── index.html └── logo.png ├── jest.config.js ├── jest.transform.js ├── jsdoc.json ├── package-lock.json ├── package.json ├── src ├── index.js ├── resize-observer-directive.js └── utils.js ├── test └── unit │ ├── index.spec.js │ └── resize.component.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env" 5 | ] 6 | ], 7 | } 8 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | plugins: ['html', 'prettier', 'vue'], 7 | 8 | rules: { 9 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 10 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 11 | 'prettier/prettier': [ 12 | 'warn', 13 | { 14 | // xx个字符后折行 15 | printWidth: 160, 16 | // 单引号 17 | singleQuote: true, 18 | // 尾随逗号 19 | trailingComma: 'none', 20 | // Tab占用2个空格 21 | tabWidth: 2, 22 | // 是否使用制表符:true-制表符,false-空格 23 | useTabs: false, 24 | // 括号添加空格 25 | bracketSpacing: true, 26 | // 箭头函数参数始终放置圆括号 27 | arrowParens: 'always', 28 | // JSX. 把>放置在最后一行末尾 29 | jsxBracketSameLine: true, 30 | // 语句末尾添加分号 31 | semi: true, 32 | // 括号 33 | bracesSpacing: true, 34 | // 数组展开 35 | arrayExpand: true, 36 | // JSX单引号 37 | jsxSingleQuote: false, 38 | // 更好的错误信息输出 39 | openOutput: true, 40 | // 匿名函数省略空格 41 | noSpaceEmptyFn: true, 42 | // 函数括号前始终插入空格 43 | spaceBeforeFunctionParen: true, 44 | // 三元运算 45 | flattenTernaries: true, 46 | // 允许属性名和值断行 47 | breakProperty: true, 48 | // else放置在新行 49 | breakBeforeElse: true, 50 | // 滚动到错误航 51 | autoScroll: true, 52 | // 对齐冒号 53 | alignObjectProperties: true, 54 | // 样式文件启用 55 | cssEnable: ['css', 'less', 'scss', 'postcss'], 56 | // GraphQL 57 | graphqlEnable: ['graphql'], 58 | // JavaScript 启用 59 | javascriptEnable: [], 60 | // JSON启用 61 | jsonEnable: ['json'], 62 | // TypeScript启用 63 | typescriptEnable: [] 64 | } 65 | ] 66 | }, 67 | parserOptions: { 68 | parser: 'babel-eslint', 69 | ecmaVersion: 7, 70 | sourceType: 'module', 71 | ecmaFeatures: { 72 | jsx: true, 73 | classes: true, 74 | defaultParams: true, 75 | modules: true 76 | } 77 | }, 78 | extends: [ 79 | 'plugin:vue/base', 80 | 'eslint-config-prettier', 81 | 'eslint:recommended', 82 | 'plugin:prettier/recommended', 83 | 'plugin:vue/essential', 84 | '@vue/prettier' 85 | ], 86 | overrides: [ 87 | { 88 | files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)'], 89 | env: { 90 | jest: true 91 | } 92 | } 93 | ] 94 | }; 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /docs 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 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 wangweiwei 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.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | logo 4 | 5 |

6 | 7 |

Vue Resize Observer

8 | 9 |

10 | 11 | Latest Version on NPM 12 | 13 | 14 | vue2 15 | 16 | 17 | vue3 18 | 19 | 20 | Issue 21 | 22 | 23 | Software License 24 | 25 | 26 | Contributors Anon 27 | 28 | 29 | Code Size 30 | 31 |
32 | 33 | Downloads 34 | 35 | 36 | Languages Count 37 | 38 | 39 | Languages 40 | 41 | 42 | Examle Online 43 | 44 |

45 | 46 | English | [简体中文](https://github.com/wangweiwei/vue-resize-observer/blob/master/README.zh.md) 47 | 48 | ![demo gif](https://github.com/wangweiwei/vue-resize-observer/raw/master/example/demo.gif) 49 | 50 | ## Installation 51 | 52 | - Vue3.0 53 | ``` sh 54 | npm install --save vue-resize-observer@next 55 | ``` 56 | - Vue2.0 57 | ``` sh 58 | npm install --save vue-resize-observer 59 | ``` 60 | 61 | ## Module import & Example 62 | 63 | * Import the package and install it into Vue: 64 | 65 | ``` js 66 | const VueResizeObserver = require("vue-resize-observer"); 67 | // Vue3.0 68 | const app = createApp(App) 69 | app.use(VueResizeObserver) // use is a instance's method & be called before mount 70 | app.mount('#app') 71 | // Vue2.0 72 | Vue.use(VueResizeObserver); // use is a static method 73 | ``` 74 | 75 | or 76 | 77 | ``` js 78 | import VueResizeObserver from "vue-resize-observer"; 79 | Vue.use(VueResizeObserver); 80 | // Vue3.0 81 | const app = createApp(App) 82 | app.use(VueResizeObserver) // use is a instance's method & be called before mount 83 | app.mount('#app') 84 | // Vue2.0 85 | Vue.use(VueResizeObserver); // use is a static method 86 | ``` 87 | 88 | or 89 | 90 | ``` js 91 | import VueResizeObserver from "vue-resize-observer"; 92 | // Vue3.0 93 | Vue.createApp({ 94 | directives: { 'resize': VueResizeObserver }, 95 | }) 96 | // Vue2.0 97 | new Vue({ 98 | directives: { 'resize': VueResizeObserver }, 99 | }) 100 | ``` 101 | 102 | 103 | * Then `v-resize` directive to detect DOM resize events. 104 | ``` vue 105 | 110 | 111 | 131 | 132 | 145 | ``` 146 | 147 | ## Example 148 | 149 | [![Example Online](https://img.shields.io/badge/-Example--Online-blue?style=for-the-badge&logo=internet-explorer)](https://www.ellow.cn/examples/vue-resize-observer/index.html) 150 | 151 | ## Documentation 152 | 153 | ``` sh 154 | npm run doc 155 | ``` 156 | 157 | Or read the documentation online 158 | 159 | [![Read the Docs Online](https://img.shields.io/badge/-Read--the--Docs--Online-blue?style=for-the-badge&logo=read-the-docs)](https://www.ellow.cn/docs/vue-resize-observer/index.html) 160 | 161 | ## ⚠️ Notice 162 | 163 | Set the `v-resize` directive for a DOM element and make the element position to something other than 'static' (for example 'relative'). 164 | 165 | ## Dependency 166 | 167 | [![Dependency Status](https://david-dm.org/wangweiwei/vue-resize-observer.svg)](https://david-dm.org/wangweiwei/vue-resize-observer) 168 | [![devDependency Status](https://david-dm.org/wangweiwei/vue-resize-observer/dev-status.svg)](https://david-dm.org/wangweiwei/vue-resize-observer?type=dev) 169 | 170 | ## License 171 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://github.com/wangweiwei/vue-resize-observer/blob/master/LICENSE) 172 | 173 | Copyright (c) 2020-present, Wayne 174 | -------------------------------------------------------------------------------- /README.zh.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | logo 4 | 5 |

6 | 7 |

Vue Resize Observer

8 | 9 |

10 | 11 | Latest Version on NPM 12 | 13 | 14 | vue2 15 | 16 | 17 | vue3 18 | 19 | 20 | Issue 21 | 22 | 23 | Software License 24 | 25 | 26 | Contributors Anon 27 | 28 | 29 | Code Size 30 | 31 |
32 | 33 | Downloads 34 | 35 | 36 | Languages Count 37 | 38 | 39 | Languages 40 | 41 | 42 | Examle Online 43 | 44 |

45 | 46 | [English](https://github.com/wangweiwei/vue-resize-observer/blob/master/README.md) | 简体中文 47 | 48 | > Vue普通元素resize事件监听,借鉴自[Cross-Browser, Event-based, Element Resize Detection](http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/) 49 | 50 | ![demo gif](https://github.com/wangweiwei/vue-resize-observer/raw/master/example/demo.gif) 51 | 52 | ## 安装 53 | 54 | - Vue3.0 55 | ``` sh 56 | npm install --save vue-resize-observer@next 57 | ``` 58 | - Vue2.0 59 | ``` sh 60 | npm install --save vue-resize-observer 61 | ``` 62 | 63 | ## 使用 64 | 65 | * 在入口文件(比如:`main.js`)中引入并`use` 66 | ``` js 67 | const VueResizeObserver = require("vue-resize-observer"); 68 | Vue.use(VueResizeObserver); 69 | // Vue3.0 70 | const app = createApp(App) 71 | app.use(VueResizeObserver) // use is a instance's method & be called before mount 72 | app.mount('#app') 73 | // Vue2.0 74 | Vue.use(VueResizeObserver); // use is a static method 75 | ``` 76 | 77 | or 78 | 79 | ``` js 80 | import VueResizeObserver from "vue-resize-observer"; 81 | Vue.use(VueResizeObserver); 82 | // Vue3.0 83 | const app = createApp(App) 84 | app.use(VueResizeObserver) // use is a instance's method & be called before mount 85 | app.mount('#app') 86 | // Vue2.0 87 | Vue.use(VueResizeObserver); // use is a static method 88 | ``` 89 | 90 | 91 | * Then `v-resize` directive to detect DOM resize events. 92 | ``` vue 93 | 98 | 99 | 160 | 161 | 174 | ``` 175 | 176 | ## 开发 177 | 178 | * 运行 179 | ``` sh 180 | npm run dev 181 | ``` 182 | 183 | * 打开:[http://localhost:8080](http://localhost:8080/) 184 | 185 | ## 例子 186 | 187 | [![Example Online](https://img.shields.io/badge/-在线例子-blue?style=for-the-badge&logo=internet-explorer)](https://www.ellow.cn/examples/vue-resize-observer/index.html) 188 | 189 | ## 开发文档 190 | 191 | * 文档生成 192 | ``` sh 193 | npm run doc 194 | ``` 195 | 196 | 或者阅读在线文档 197 | 198 | [![Read the Docs Online](https://img.shields.io/badge/-阅读在线文档-blue?style=for-the-badge&logo=read-the-docs)](https://www.ellow.cn/docs/vue-resize-observer/index.html) 199 | 200 | * 打开位于`docs`的`index.html`即可查看开发文档 201 | 202 | ## ⚠️ 注意 203 | 204 | 在当前元素的`position`有且只有在`static`的情况下,会改变当前元素的`position`属性为`relative`,所以对此属性敏感的元素需要谨慎对待! 205 | 206 | ## 依赖 207 | 208 | [![Dependency Status](https://david-dm.org/wangweiwei/vue-resize-observer.svg)](https://david-dm.org/wangweiwei/vue-resize-observer) 209 | [![devDependency Status](https://david-dm.org/wangweiwei/vue-resize-observer/dev-status.svg)](https://david-dm.org/wangweiwei/vue-resize-observer?type=dev) 210 | 211 | ## (MIT)开源协议 212 | 213 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://github.com/wangweiwei/vue-resize-observer/blob/master/LICENSE) 214 | -------------------------------------------------------------------------------- /build/dev.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var DevServer = require("webpack-dev-server"); 3 | var config = require("./webpack.config.dev.js"); 4 | config.entry.example.unshift( 5 | "webpack-dev-server/client?http://localhost:8080/", 6 | "webpack/hot/dev-server" 7 | ); 8 | var compiler = webpack(config); 9 | var server = new DevServer(compiler, { 10 | contentBase: "./example/", 11 | clientLogLevel: "info", 12 | stats: { 13 | chunks: true, 14 | colors: true 15 | } 16 | }); 17 | 18 | server.listen(8080); 19 | 20 | module.exports = server; 21 | -------------------------------------------------------------------------------- /build/rollup.config.prod.js: -------------------------------------------------------------------------------- 1 | import buble from "rollup-plugin-buble"; 2 | import commonjs from "rollup-plugin-commonjs"; 3 | import resolve from "rollup-plugin-node-resolve"; 4 | import cleanup from "rollup-plugin-cleanup"; 5 | 6 | export default { 7 | input: "./src", 8 | output: { 9 | file: "./dist/index.js", 10 | format: "umd", 11 | name: "__vue_resize_observer__", 12 | sourcemap: true 13 | }, 14 | 15 | plugins: [ 16 | buble(), 17 | resolve({ 18 | jsnext: true, 19 | main: true 20 | }), 21 | commonjs(), 22 | cleanup() 23 | ] 24 | }; 25 | -------------------------------------------------------------------------------- /build/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require("webpack"); 3 | 4 | const config = { 5 | mode: "development", 6 | entry: { 7 | example: ["./example/example.js"], 8 | vendor: ["vue"] 9 | }, 10 | output: { 11 | path: path.resolve(__dirname, "../example"), 12 | library: "__vue_resize_observer__", 13 | libraryTarget: "umd", 14 | filename: "[name].build.js" 15 | }, 16 | resolve: { 17 | alias: { 18 | vue$: "vue" 19 | } 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.js$/, 25 | loader: ["babel-loader"], 26 | exclude: path.resolve(__dirname, "../node_modules") 27 | }, 28 | { 29 | test: /\.css$/, 30 | loader: "style-loader!css-loader", 31 | exclude: path.resolve(__dirname, "../node_modules") 32 | } 33 | ] 34 | }, 35 | plugins: [ 36 | new webpack.HotModuleReplacementPlugin(), 37 | new webpack.DefinePlugin({ 38 | "process.env": { 39 | NODE_ENV: process.env.NODE_ENV 40 | ? JSON.stringify(process.env.NODE_ENV) 41 | : "'development'" 42 | } 43 | }) 44 | ], 45 | optimization: { 46 | splitChunks: { 47 | cacheGroups: { 48 | commons: { 49 | test: /[\\/]node_modules[\\/]/, 50 | name: "vue", 51 | chunks: "all" 52 | } 53 | } 54 | } 55 | }, 56 | devtool: "cheap-module-eval-source-map", 57 | performance: { 58 | hints: false 59 | } 60 | }; 61 | 62 | module.exports = config; 63 | -------------------------------------------------------------------------------- /example/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangweiwei/vue-resize-observer/1c8b40b9a9fea3587dd25d4b01a57cc88f9324e8/example/demo.gif -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | var app = Vue.createApp({ 2 | el: "#app", 3 | data() { 4 | return { 5 | width1: 0, 6 | height1: 0, 7 | 8 | width2: 0, 9 | height2: 0 10 | }; 11 | }, 12 | mounted() { 13 | this.width1 = this.$refs.resize1.offsetWidth; 14 | this.height1 = this.$refs.resize1.offsetHeight; 15 | 16 | this.width2 = this.$refs.resize2.offsetWidth; 17 | this.height2 = this.$refs.resize2.offsetHeight; 18 | }, 19 | methods: { 20 | onResize1({ width, height }) { 21 | this.width1 = width; 22 | this.height1 = height; 23 | }, 24 | onResize2({ width, height }) { 25 | this.width2 = width; 26 | this.height2 = height; 27 | } 28 | } 29 | }); 30 | app.use(window.__vue_resize_observer__); 31 | app.mount("#app"); 32 | -------------------------------------------------------------------------------- /example/index.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | .py-5 h1 { 6 | text-align: center; 7 | border-bottom: 1px solid #eaecef; 8 | padding-bottom: 20px; 9 | } 10 | #app { 11 | margin: 0 auto; 12 | width: 100%; 13 | 14 | display: -webkit-box; 15 | display: -webkit-flex; 16 | display: -ms-flexbox; 17 | display: -moz-box; 18 | display: -moz-flex; 19 | display: flex; 20 | 21 | -webkit-box-pack: justify; 22 | -webkit-justify-content: space-between; 23 | -moz-box-pack: justify; 24 | -moz-justify-content: space-between; 25 | -ms-flex-pack: justify; 26 | justify-content: space-between; 27 | 28 | -webkit-box-align: center; 29 | -webkit-align-items: center; 30 | -moz-box-align: center; 31 | -moz-align-items: center; 32 | -ms-flex-align: center; 33 | align-items: center; 34 | 35 | flex-wrap: wrap; 36 | } 37 | 38 | .test1, .test2 { 39 | background-color: #39495C; 40 | color: #64B587; 41 | width: 300px; 42 | height: 300px; 43 | margin: 0 auto; 44 | margin-top: 10px; 45 | resize: both; 46 | overflow: auto; 47 | 48 | display: -webkit-box; 49 | display: -webkit-flex; 50 | display: -ms-flexbox; 51 | display: -moz-box; 52 | display: -moz-flex; 53 | display: flex; 54 | 55 | 56 | -webkit-box-pack: center; 57 | -webkit-justify-content: center; 58 | -moz-box-pack: center; 59 | -moz-justify-content: center; 60 | -ms-flex-pack: center; 61 | justify-content: center; 62 | 63 | -webkit-box-align: center; 64 | -webkit-align-items: center; 65 | -moz-box-align: center; 66 | -moz-align-items: center; 67 | -ms-flex-align: center; 68 | align-items: center; 69 | } 70 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue Resize Observer 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 35 | 36 | 37 | 38 |
39 |
40 | vue 43 |

Vue Resize Observer

44 |
45 |
46 |
47 | width: {{width1}}, height: {{height1}} 48 |
49 |
50 | width: {{width2}}, height: {{height2}} 51 |
52 |
53 | 76 |
77 | 78 | 79 | 80 | 85 | 90 | 95 | 96 | 121 | 122 | 126 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /example/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangweiwei/vue-resize-observer/1c8b40b9a9fea3587dd25d4b01a57cc88f9324e8/example/logo.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue' 7 | ], 8 | transform: { 9 | '^.+\\.vue$': require.resolve('vue-jest'), 10 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': require.resolve('jest-transform-stub'), 11 | '^.+\\.jsx?$': require.resolve('./jest.transform.js') 12 | }, 13 | globals: { 14 | 'vue-jest': { 15 | babelConfig: { 16 | plugins: [require('babel-plugin-transform-es2015-modules-commonjs')] 17 | } 18 | } 19 | }, 20 | snapshotSerializers: [ 21 | 'jest-serializer-vue' 22 | ], 23 | transformIgnorePatterns: ['/node_modules/'], 24 | moduleNameMapper: { 25 | '^@/(.*)$': '/src/$1', 26 | }, 27 | testEnvironment: 'jest-environment-jsdom-fifteen', 28 | testMatch: [ 29 | '**/test/**/*.spec.[jt]s?(x)', 30 | '**/__tests__/*.[jt]s?(x)' 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /jest.transform.js: -------------------------------------------------------------------------------- 1 | const babelJest = require('babel-jest') 2 | 3 | module.exports = babelJest.createTransformer({ 4 | plugins: ['@babel/plugin-transform-modules-commonjs'], 5 | babelrc: false, 6 | configFile: false 7 | }) 8 | 9 | -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": false 4 | }, 5 | "source": { 6 | "include": "src/", 7 | "includePattern": ".js$", 8 | "excludePattern": "(node_modules/|docs)" 9 | }, 10 | "plugins": [ 11 | "plugins/markdown" 12 | ], 13 | "opts": { 14 | "template": "node_modules/docdash", 15 | "encoding": "utf8", 16 | "destination": "docs/", 17 | "recurse": true, 18 | "verbose": true 19 | }, 20 | "templates": { 21 | "cleverLinks": false, 22 | "monospaceLinks": false 23 | }, 24 | "docdash": { 25 | "sort": false, 26 | "sectionOrder": [ 27 | "Classes", 28 | "Modules", 29 | "Externals", 30 | "Events", 31 | "Namespaces", 32 | "Mixins", 33 | "Tutorials", 34 | "Interfaces" 35 | ], 36 | "typedefs": true 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-resize-observer", 3 | "version": "2.0.15", 4 | "description": "Vue普通元素resize事件监听", 5 | "main": "dist/index.js", 6 | "files": [ 7 | "dist/index.js ", 8 | "dist/index.js.map", 9 | "dist/index.min.js" 10 | ], 11 | "keywords": [ 12 | "vue", 13 | "vue3", 14 | "plugin", 15 | "resize", 16 | "event", 17 | "observer" 18 | ], 19 | "scripts": { 20 | "build": "node_modules/.bin/rollup -m -c build/rollup.config.prod.js && uglifyjs dist/index.js -c -m > dist/index.min.js", 21 | "dev": "node build/dev.js", 22 | "doc": "./node_modules/.bin/jsdoc ./README.md -c ./jsdoc.json", 23 | "test:unit": "jest", 24 | "format:src": "prettier-eslint --write \"src/**/*.html\" \"src/**/*.scss\" \"src/**/*.js\" \"src/**/*.vue\"", 25 | "format:build": "prettier-eslint --write \"build/**/*.html\" \"build/**/*.scss\" \"build/**/*.js\" \"build/**/*.vue\"", 26 | "format:example": "prettier-eslint --write \"example/**/*.html\" \"example/**/*.scss\" \"example/**/*.js\" \"example/**/*.vue\"", 27 | "format:test": "prettier-eslint --write \"test/**/*.html\" \"test/**/*.scss\" \"test/**/*.js\" \"test/**/*.vue\"", 28 | "format": "npm run format:src & npm run format:build & npm run format:example & npm run format:test" 29 | }, 30 | "dependencies": {}, 31 | "devDependencies": { 32 | "@babel/plugin-transform-modules-commonjs": "^7.8.3", 33 | "@babel/preset-env": "^7.8.3", 34 | "@testing-library/jest-dom": "^5.11.8", 35 | "@vue/cli-plugin-unit-jest": "^4.1.2", 36 | "@vue/compiler-sfc": "^3.0.5", 37 | "@vue/test-utils": "^2.0.0-beta.13", 38 | "babel-loader": "^8.0.6", 39 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", 40 | "buble": "^0.19.8", 41 | "css-loader": "^3.4.1", 42 | "docdash": "^1.1.1", 43 | "hammerjs": "^2.0.8", 44 | "jest-environment-jsdom-fifteen": "^1.0.2", 45 | "jest-serializer-vue": "^2.0.2", 46 | "jsdoc": "^3.6.3", 47 | "prettier": "^1.19.1", 48 | "prettier-eslint-cli": "^5.0.0", 49 | "rollup": "^2.36.1", 50 | "rollup-plugin-buble": "^0.19.8", 51 | "rollup-plugin-cleanup": "^3.1.1", 52 | "rollup-plugin-commonjs": "^10.1.0", 53 | "rollup-plugin-node-resolve": "^5.2.0", 54 | "style-loader": "^1.1.2", 55 | "uglify-js": "^3.7.4", 56 | "vue": "3.0.0", 57 | "vue-jest": "^5.0.0-alpha.7", 58 | "vue-loader": "^15.8.3", 59 | "vue-template-compiler": "2.0.0", 60 | "webpack": "^4.41.5", 61 | "webpack-dev-server": "^3.10.1" 62 | }, 63 | "peerDependencies": {}, 64 | "repository": { 65 | "type": "git", 66 | "url": "git+https://github.com/wangweiwei/vue-resize-observer.git" 67 | }, 68 | "bugs": { 69 | "url": "https://github.com/wangweiwei/vue-resize-observer/issues" 70 | }, 71 | "homepage": "https://github.com/wangweiwei/vue-resize-observer#readme", 72 | "author": "wangweiwei", 73 | "license": "MIT" 74 | } 75 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { resizeObserverDirective } from "./resize-observer-directive"; 2 | 3 | /** 4 | * vue resize事件对象 5 | * 6 | * @module vueResizeObserver 7 | */ 8 | const vueResizeObserver = resizeObserverDirective 9 | 10 | /** 11 | * 插件安装方法 12 | * 13 | * @param Vue {Vue} Vue构造器 14 | * 15 | * @function install 16 | */ 17 | resizeObserverDirective.install = function (app) { 18 | app.directive("resize", resizeObserverDirective); 19 | } 20 | 21 | export default vueResizeObserver; 22 | 23 | /** 24 | * 模块处理 25 | */ 26 | if (typeof exports == "object") { 27 | module.exports = vueResizeObserver; 28 | } else if (typeof define == "function" && define.amd) { 29 | define([], function() { 30 | return vueResizeObserver; 31 | }); 32 | } else if (typeof window !== "undefined" && window.Vue) { 33 | window.__vue_resize_observer__ = vueResizeObserver; 34 | // Vue.use(vueResizeObserver); 35 | } 36 | -------------------------------------------------------------------------------- /src/resize-observer-directive.js: -------------------------------------------------------------------------------- 1 | import { isIE, $requestAnimationFrame, $cancelAnimationFrame } from "./utils"; 2 | 3 | /** 4 | * resize处理器,此处回调元素v-resize的方法 5 | * 6 | * @param event {Event} resizeTrigge的resize事件传递,来自{@link registereResizeHandler}方法中产生的事件源 7 | * 8 | * @function 9 | * 10 | * @see {@link registereResizeHandler} 11 | */ 12 | function resizeHandler(event) { 13 | event.__currentTarget__ = this.__container__; 14 | this.__resize__handler__.call( 15 | this, 16 | { 17 | width: this.offsetWidth, 18 | height: this.offsetHeight 19 | }, 20 | event 21 | ); 22 | } 23 | 24 | /** 25 | * 优化resize监听 26 | * 27 | * @param event {Event} resizeTrigge的resize事件传递,来自{@link registereResizeHandler}方法中产生的事件源 28 | * 29 | * @function 30 | * 31 | * @see {@link https://developer.mozilla.org/zh-CN/docs/Web/API/Window/resize_event} 32 | * 33 | * @see {@link registereResizeHandler} 34 | */ 35 | function resizeTriggerListener(event) { 36 | const _resizeTrigger = event.currentTarget || event.srcElement; 37 | if (_resizeTrigger.__requestAnimationFrameID__) { 38 | $cancelAnimationFrame(_resizeTrigger.__requestAnimationFrameID__); 39 | } 40 | _resizeTrigger.__requestAnimationFrameID__ = $requestAnimationFrame( 41 | resizeHandler.bind(this, event) 42 | ); 43 | } 44 | 45 | /** 46 | * 注册resize处理方法 47 | * 48 | * @function 49 | */ 50 | function registereResizeHandler() { 51 | if (document.attachEvent) { 52 | this.__container__.attachEvent("onresize", resizeHandler.bind(this)); 53 | } else { 54 | this.contentDocument.defaultView.addEventListener( 55 | "resize", 56 | resizeTriggerListener.bind(this) 57 | ); 58 | } 59 | } 60 | 61 | /** 62 | * 创建resize真正的触发器 63 | * 64 | * @function 65 | */ 66 | function createResizeTrigger() { 67 | const object = document.createElement("object"); 68 | object.setAttribute("aria-hidden", "true"); 69 | object.setAttribute("tabindex", -1); 70 | const objectStyle = ` 71 | display: block !important; 72 | position: absolute !important; 73 | top: 0 !important; 74 | left: 0 !important; 75 | width: 100% !important; 76 | height: 100% !important; 77 | overflow: hidden !important; 78 | pointer-events: none !important; 79 | z-index: -1 !important; 80 | opacity: 0 !important; 81 | `; 82 | object.setAttribute("style", objectStyle); 83 | object.type = "text/html"; 84 | 85 | return object; 86 | } 87 | 88 | /** 89 | * resize观察者指令对象 90 | * 91 | * @module resizeObserverDirective 92 | */ 93 | export const resizeObserverDirective = { 94 | /** 95 | * bind → beforeMount 96 | * 97 | * @param el {Element} 操作的元素 98 | * @param binding {Object} 一些绑定相关的值 99 | * @param vnode {VNode} Vue 编译生成的虚拟节点 100 | * @param oldVnode {VNode} 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。 101 | * 102 | * @function beforeMount 103 | */ 104 | beforeMount(el, binding, vnode, oldVnode) { 105 | // 获得真正的触发器 106 | el.__resizeTrigger__ = createResizeTrigger(); 107 | el.__resizeTrigger__.__container__ = el; 108 | el.__resizeTrigger__.__resize__handler__ = binding.value; 109 | el.__resizeTrigger__.onload = registereResizeHandler; 110 | 111 | // 将真正的触发器作为子元素添加到当前元素 112 | const _isIE = isIE(); 113 | _isIE && el.appendChild(el.__resizeTrigger__); 114 | el.__resizeTrigger__.data = "about:blank"; 115 | !_isIE && el.appendChild(el.__resizeTrigger__); 116 | }, 117 | 118 | /** 119 | * inserted → mounted 120 | * 121 | * @param el {Element} 操作的元素 122 | * @param binding {Object} 一些绑定相关的值 123 | * @param vnode {VNode} Vue 编译生成的虚拟节点 124 | * @param oldVnode {VNode} 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。 125 | * 126 | * @function mounted 127 | */ 128 | mounted(el, binding, vnode, oldVnode) { 129 | if (getComputedStyle(el).position === "static") { 130 | el.style.setProperty("position", "relative", "important"); 131 | } 132 | }, 133 | 134 | /** 135 | * beforeUpdate (new) 136 | * 137 | * @param el {Element} 操作的元素 138 | * @param binding {Object} 一些绑定相关的值 139 | * @param vnode {VNode} Vue 编译生成的虚拟节点 140 | * @param oldVnode {VNode} 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。 141 | * 142 | * @function beforeUpdate 143 | */ 144 | beforeUpdate(el, binding, vnode, oldVnode) {}, 145 | 146 | /** 147 | * update (has been removed) 148 | * componentUpdated → updated 149 | * 150 | * @param el {Element} 操作的元素 151 | * @param binding {Object} 一些绑定相关的值 152 | * @param vnode {VNode} Vue 编译生成的虚拟节点 153 | * @param oldVnode {VNode} 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。 154 | * 155 | * @function updated 156 | */ 157 | updated(el, binding, vnode, oldVnode) { 158 | var inserted = false; 159 | for (var i = 0, length = el.children.length; i < length; i++) { 160 | if (el.children[i] === el.__resizeTrigger__) { 161 | inserted = true; 162 | } 163 | } 164 | if (!inserted) { 165 | var _isIE = isIE(); 166 | _isIE && el.appendChild(el.__resizeTrigger__); 167 | el.__resizeTrigger__.data = "about:blank"; 168 | !_isIE && el.appendChild(el.__resizeTrigger__); 169 | } 170 | }, 171 | 172 | /** 173 | * beforeUnmount (new) 174 | * 175 | * @param el {Element} 操作的元素 176 | * @param binding {Object} 一些绑定相关的值 177 | * @param vnode {VNode} Vue 编译生成的虚拟节点 178 | * @param oldVnode {VNode} 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。 179 | * 180 | * @function beforeMount 181 | */ 182 | beforeUnmount(el, binding, vnode, oldVnode) {}, 183 | /** 184 | * unbind -> unmounted 185 | * 186 | * @param el {Element} 操作的元素 187 | * @param binding {Object} 一些绑定相关的值 188 | * @param vnode {VNode} Vue 编译生成的虚拟节点 189 | * @param oldVnode {VNode} 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。 190 | * 191 | * @function unmounted 192 | */ 193 | unmounted(el, binding, vnode, oldVnode) { 194 | if (document.attachEvent) { 195 | el.detachEvent("onresize", resizeHandler); 196 | } else if (el.__resizeTrigger__ && el.__resizeTrigger__.contentDocument) { 197 | el.__resizeTrigger__.contentDocument.defaultView.removeEventListener( 198 | "resize", 199 | resizeTriggerListener 200 | ); 201 | el.__resizeTrigger__ = !el.removeChild(el.__resizeTrigger__); 202 | } 203 | } 204 | }; 205 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 是否是IE浏览器的判断 3 | * 4 | * @function 5 | * 6 | * @return {Boolean} 是否IE浏览器的判断结果 7 | */ 8 | export function isIE() { 9 | let _isIE = false; 10 | if (typeof navigator !== "undefined") { 11 | _isIE = 12 | navigator.userAgent.match(/Trident/) || navigator.userAgent.match(/Edge/); 13 | } 14 | return _isIE; 15 | } 16 | 17 | var lastTime = 0; 18 | var vendors = ["ms", "moz", "webkit", "o"]; 19 | if (typeof window === "undefined") { 20 | global.window = {}; 21 | } 22 | for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 23 | window.requestAnimationFrame = window[vendors[x] + "RequestAnimationFrame"]; 24 | window.cancelAnimationFrame = 25 | window[vendors[x] + "CancelAnimationFrame"] || 26 | window[vendors[x] + "CancelRequestAnimationFrame"]; 27 | } 28 | 29 | if (!window.requestAnimationFrame) { 30 | window.requestAnimationFrame = function(callback, element) { 31 | var currTime = new Date().getTime(); 32 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 33 | var id = window.setTimeout(function() { 34 | callback(currTime + timeToCall); 35 | }, timeToCall); 36 | lastTime = currTime + timeToCall; 37 | return id; 38 | }; 39 | } 40 | 41 | if (!window.cancelAnimationFrame) { 42 | window.cancelAnimationFrame = function(id) { 43 | clearTimeout(id); 44 | }; 45 | } 46 | 47 | /** 48 | * requestAnimationFrame的兼容处理 49 | * 50 | * @see {@link https://gist.github.com/paulirish/1579671} 51 | */ 52 | export const $requestAnimationFrame = window.requestAnimationFrame; 53 | 54 | /** 55 | * cancelAnimationFrame的兼容处理 56 | * 57 | * @see {@link https://gist.github.com/paulirish/1579671} 58 | */ 59 | export const $cancelAnimationFrame = window.cancelAnimationFrame; 60 | -------------------------------------------------------------------------------- /test/unit/index.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from "@vue/test-utils"; 2 | import jtest from "vue-jest"; 3 | import ResizeComponent from "./resize.component"; 4 | import ResizeObserver from "../../src/index"; 5 | import { resizeObserverDirective } from "../../src/resize-observer-directive"; 6 | import "@testing-library/jest-dom"; 7 | 8 | describe("object的插入及其样式、属性", () => { 9 | const wrapper = mount(ResizeComponent, { 10 | global: { 11 | directives: { 12 | resize: resizeObserverDirective 13 | } 14 | } 15 | }); 16 | 17 | // const resizeWrapper = wrapper.find("#resize"); 18 | // const objectWrapper = wrapper.find("#resize object"); 19 | 20 | test("测试插件能否正常安装", () => { 21 | const installed = jest.fn(); 22 | const Plugin = { 23 | install(Vue) { 24 | Vue.directive("resize", resizeObserverDirective); 25 | installed(); 26 | } 27 | }; 28 | 29 | mount(ResizeComponent, { 30 | global: { 31 | plugins: [Plugin] 32 | } 33 | }); 34 | 35 | expect(installed).toHaveBeenCalled(); 36 | }); 37 | 38 | // todo: 测试Object及其属性是否正常 39 | /** 40 | it("object是否正常插入", () => { 41 | expect(resizeWrapper.element).toContainElement(objectWrapper.element); 42 | }); 43 | 44 | it("object的属性是否正常", () => { 45 | expect(objectWrapper.element).toHaveAttribute("aria-hidden"); 46 | expect(objectWrapper.element).toHaveAttribute("tabindex"); 47 | expect(objectWrapper.element).toHaveAttribute("style"); 48 | }); 49 | 50 | it("object的样式是否正常", () => { 51 | expect(objectWrapper.element).toHaveStyle("display: block;"); 52 | expect(objectWrapper.element).toHaveStyle("position: absolute;"); 53 | expect(objectWrapper.element).toHaveStyle("top: 0;"); 54 | expect(objectWrapper.element).toHaveStyle("left: 0;"); 55 | expect(objectWrapper.element).toHaveStyle("width: 100%;"); 56 | expect(objectWrapper.element).toHaveStyle("height: 100%;"); 57 | expect(objectWrapper.element).toHaveStyle("overflow: hidden;"); 58 | expect(objectWrapper.element).toHaveStyle("pointer-events: none;"); 59 | expect(objectWrapper.element).toHaveStyle("z-index: -1;"); 60 | expect(objectWrapper.element).toHaveStyle("opacity: 0;"); 61 | }); 62 | */ 63 | }); 64 | -------------------------------------------------------------------------------- /test/unit/resize.component.js: -------------------------------------------------------------------------------- 1 | export default { 2 | template: ` 3 |
7 | width: {{width}}, height: {{height}} 8 |
9 | `, 10 | 11 | data() { 12 | return { 13 | width: 0, 14 | height: 0 15 | }; 16 | }, 17 | 18 | methods: { 19 | onResize({ width, height }) { 20 | this.width = width; 21 | this.height = height; 22 | } 23 | } 24 | }; 25 | --------------------------------------------------------------------------------