Repository: heavyy/vue-intersect Branch: master Commit: 0a8123f30748 Files: 12 Total size: 17.7 KB Directory structure: gitextract_ucdwe_b3/ ├── .babelrc ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── __mocks__/ │ └── intersection-observer.js ├── __tests__/ │ └── index.js ├── dist/ │ └── index.js ├── package.json └── src/ └── index.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ ["env", { "modules": false, "targets": { "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] } }] ], "plugins": ["transform-runtime"], "env": { "test": { "presets": ["env"] } } } ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 indent_style = space indent_size = 2 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true ================================================ FILE: .eslintrc.js ================================================ module.exports = { root: true, parser: 'babel-eslint', parserOptions: { sourceType: 'module' }, env: { browser: true, }, extends: 'standard', 'rules': { 'arrow-parens': 0 } } ================================================ FILE: .gitignore ================================================ .DS_Store node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* coverage/ ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - "6" - "7" - "8" after_script: "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2017 Heavyy IvS Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Vue Intersect **A Vue component to add intersection-observer to a Vue component or HTML element.** [![npm version](https://badge.fury.io/js/vue-intersect.svg)](https://badge.fury.io/js/vue-intersect) [![Coverage Status](https://coveralls.io/repos/github/heavyy/vue-intersect/badge.svg)](https://coveralls.io/github/heavyy/vue-intersect) [![Build status](https://img.shields.io/travis/heavyy/vue-intersect.svg)](https://travis-ci.org/heavyy/vue-intersect) ## Table of content * [Introduction](#introduction) * [Demo](#demo) * [Installation](#installation) * [Usage](#usage) * [Properties](#properties) * [Events](#events) * [Polyfill](#polyfill) ## Introduction The [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) is an amazing API which allows you to observe one or more HTMLElement for when it has entered or left the viewport. This API has many use cases like, infinite-scroll, lazy-loading or animations when an element enters the viewport. ## Demo We've made a basic demo of how you might want to use **vue-intersect**. The code is available in the [gh-pages branch](https://github.com/heavyy/vue-intersect/tree/gh-pages) and the part where **vue-intersect** is used [can be found here](https://github.com/heavyy/vue-intersect/blob/gh-pages/src/components/InfiniteScrollItem.vue#L33). [Hackernews infinite scroll demo](https://heavyy.github.io/vue-intersect/) > Please keep in mind that the demo is not production code. Use it as an inspiration. ## Installation Simply install using your favorite package manager 🔥 > ⚠️ If you're using Vue 3 then install `vue-intersect@next` #### NPM ```bash npm install vue-intersect --save ``` #### Yarn ```bash yarn add vue-intersect ``` ## Usage The package acts as an abstract component, much like what you may know from [keep-alive](https://vuejs.org/v2/api/#keep-alive) or [transition](https://vuejs.org/v2/api/#transition). This means that it's basically a "decorator". A component which does not output any markup to the DOM, but adds the functionality under the hood 😱. #### .vue ```html ``` ## Properties | Property | Type | Default | Required | Description | | ---------- | ----------- | ----------------- | -------- | ---------------------------------------- | | threshold | Array | [0, 0.2] | *no* | [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Intersection_observer_options) | | root | HTMLElement | null | *no* | [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Intersection_observer_options) | | rootMargin | String | *0px 0px 0px 0px* | *no* | [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Intersection_observer_options) | ## Events | Name | Arguments | Description | | ---------- | ---------------------------------------- | ---------------------------------------- | | **change** | [*IntersectionObserverEntry*](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) | Event fired on any inte. | | **enter** | [*IntersectionObserverEntry*](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) | Event fired when the element is intersected (visible on the screen) | | **leave** | [*IntersectionObserverEntry*](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) | Event fired when the element is *not* intersected (not visible on the screen) | | **destroyed** | None | Fired when the underlying element is destroyed | > The **enter** and **leave** event is sugar, for an often performed operation. You still have to set the threshold to e.g. [0, 0.2] (default). If you leave out "0", it will never call the **leave** event. The events is compliant with Vue's [event modifiers](https://vuejs.org/v2/guide/events.html#Event-Modifiers). This means that you could add `.once` to the events to make sure you only trigger your event handler once. ## Polyfill The IntersectionObserver API is not currently available in all browsers ([IE11, Safari and iOS Safari](http://caniuse.com/#feat=intersectionobserver)). If you intend to support these browsers, you'll need to add a poylfill to your bundle. [WICG IntersectionObserver Polyfill](https://github.com/w3c/IntersectionObserver/tree/master/polyfill) is highly recommended. ================================================ FILE: __mocks__/intersection-observer.js ================================================ export default class IntersectionObserver { constructor (cb, options) { this.cb = cb this.options = options this.observables = [] } observe (el) { this.observables.push(el) } disconnect () { return true } } ================================================ FILE: __tests__/index.js ================================================ import Vue from 'vue/dist/vue.js' import IntersectionObserver from '../__mocks__/intersection-observer.js' import Intersect from '../src' // Mock global.IntersectionObserver = IntersectionObserver test('It should be a function', () => { expect(typeof Intersect.mounted).toBe('function') }) test('It should create a instance of IntersectionObserver', async () => { global.console.warn = jest.fn() const vm = new Vue(Intersect).$mount() expect(vm.observer).toBeInstanceOf(IntersectionObserver) await vm.$nextTick expect(global.console.warn).toHaveBeenCalledTimes(1) }) test('It should mount correctly and add the item to the observers list', async () => { const mockedIntersect = Object.assign({}, Intersect) const spy = { mounted: jest.spyOn(mockedIntersect, 'mounted') } const vm = new Vue({ template: `
this is my component
`, components: {Intersect: mockedIntersect} }).$mount() await Vue.nextTick() expect(vm._vnode.componentInstance.observer.observables.length).toBe(1) expect(spy.mounted).toHaveBeenCalledTimes(1) expect(vm.$el.outerHTML).toBe(`
this is my component
`) expect(vm.$el.textContent).toBe('this is my component') }) test('It should emit "enter" event when the component is intersected', async () => { const mockedIntersect = Object.assign({}, Intersect) const spy = jest.fn() const vm = new Vue({ template: `
`, components: {Intersect: mockedIntersect}, methods: { onEnter: spy } }).$mount() await vm.$nextTick() vm._vnode.componentInstance.observer.cb([{ isIntersecting: true }]) expect(spy).toHaveBeenCalledTimes(1) }) test('It should emit "leave" event when the component is not intersected', async () => { const mockedIntersect = Object.assign({}, Intersect) const spy = jest.fn() const vm = new Vue({ template: `
`, components: {Intersect: mockedIntersect}, methods: { onLeave: spy } }).$mount() await vm.$nextTick() vm._vnode.componentInstance.observer.cb([{ isIntersecting: false }]) expect(spy).toHaveBeenCalledTimes(1) }) test('It should emit "change" on any intersection change', async () => { const mockedIntersect = Object.assign({}, Intersect) const spy = jest.fn() const vm = new Vue({ template: `
`, components: {Intersect: mockedIntersect}, methods: { onChange: spy } }).$mount() await vm.$nextTick() vm._vnode.componentInstance.observer.cb([{ isIntersecting: false }]) vm._vnode.componentInstance.observer.cb([{ isIntersecting: true }]) expect(spy).toHaveBeenCalledTimes(2) }) test('It should be possible to set the threshold property', async () => { const mockedIntersect = Object.assign({}, Intersect) const vm = new Vue({ template: `
`, components: {Intersect: mockedIntersect} }).$mount() await vm.$nextTick() expect(vm._vnode.componentInstance.$options.propsData.threshold).toEqual([0, 0.5]) }) test('It should be possible to set the root property', async () => { const mockedIntersect = Object.assign({}, Intersect) const vm = new Vue({ template: `
`, components: {Intersect: mockedIntersect}, data () { return { viewPort: document.body } } }).$mount() await vm.$nextTick() expect(vm._vnode.componentInstance.$options.propsData.root).toBeInstanceOf(HTMLElement) }) test('It should be possible to set the rootMargin property', async () => { const mockedIntersect = Object.assign({}, Intersect) const vm = new Vue({ template: `
`, components: {Intersect: mockedIntersect} }).$mount() await vm.$nextTick() expect(vm._vnode.componentInstance.$options.propsData.rootMargin).toBe('1px 1px 1px 1px') }) test('It should disconnect the IntersectionObserver when the component is destroyed', async () => { const mockedIntersect = Object.assign({}, Intersect) const spy = { destroyed: jest.spyOn(mockedIntersect, 'destroyed'), disconnect: jest.spyOn(global.IntersectionObserver.prototype, 'disconnect') } const vm = new Vue({ template: `
`, components: {Intersect: mockedIntersect} }).$mount() await vm.$nextTick() vm.$destroy() expect(spy.destroyed).toHaveBeenCalledTimes(1) expect(spy.disconnect).toHaveBeenCalledTimes(1) spy.disconnect.mockClear() }) test('It should emit event when component is destroyed', async () => { const mockedIntersect = Object.assign({}, Intersect) const onDestroy = jest.fn() const spy = { destroyed: jest.spyOn(mockedIntersect, 'destroyed'), disconnect: jest.spyOn(global.IntersectionObserver.prototype, 'disconnect') } const vm = new Vue({ template: `
`, components: {Intersect: mockedIntersect}, methods: { onDestroy } }).$mount() await vm.$nextTick() vm.$destroy() expect(spy.destroyed).toHaveBeenCalledTimes(1) expect(spy.disconnect).toHaveBeenCalledTimes(1) expect(onDestroy).toHaveBeenCalledTimes(1) }) test('It should warn when no child component is defined', async () => { global.console.warn = jest.fn() const vm = new Vue({ template: ``, components: {Intersect} }).$mount() await vm.$nextTick() expect(global.console.warn).toHaveBeenCalledTimes(1) expect(vm._vnode.componentInstance.observer.observables.length).toBe(0) global.console.warn.mockReset() }) test('It should warn if more than one child component is defined', async () => { global.console.warn = jest.fn() const vm = new Vue({ template: `
`, components: {Intersect} }).$mount() await vm.$nextTick() expect(global.console.warn).toHaveBeenCalledTimes(1) expect(vm._vnode.componentInstance.observer.observables.length).toBe(1) global.console.warn.mockReset() }) test('It should not warn if Vue.config.silent is set to false', async () => { require('vue').config.silent = true global.console.warn = jest.fn() const vm = new Vue({ template: `
`, components: {Intersect} }).$mount() await vm.$nextTick() expect(global.console.warn).toHaveBeenCalledTimes(0) expect(vm._vnode.componentInstance.observer.observables.length).toBe(1) global.console.warn.mockReset() }) ================================================ FILE: dist/index.js ================================================ import Vue from 'vue'; var warn = function warn(msg) { if (!Vue.config.silent) { console.warn(msg); } }; export default { name: 'intersect', abstract: true, props: { threshold: { type: Array, required: false, default: function _default() { return [0.2]; } }, root: { type: HTMLElement, required: false, default: function _default() { return null; } }, rootMargin: { type: String, required: false, default: function _default() { return '0px 0px 0px 0px'; } } }, created: function created() { var _this = this; this.observer = new IntersectionObserver(function (entries) { if (!entries[0].isIntersecting) { _this.$emit('leave', [entries[0]]); } else { _this.$emit('enter', [entries[0]]); } _this.$emit('change', [entries[0]]); }, { threshold: this.threshold, root: this.root, rootMargin: this.rootMargin }); }, mounted: function mounted() { var _this2 = this; this.$nextTick(function () { if (_this2.$slots.default && _this2.$slots.default.length > 1) { warn('[VueIntersect] You may only wrap one element in a component.'); } else if (!_this2.$slots.default || _this2.$slots.default.length < 1) { warn('[VueIntersect] You must have one child inside a component.'); return; } _this2.observer.observe(_this2.$slots.default[0].elm); }); }, destroyed: function destroyed() { this.observer.disconnect(); }, render: function render() { return this.$slots.default ? this.$slots.default[0] : null; } }; ================================================ FILE: package.json ================================================ { "name": "vue-intersect", "version": "1.1.5", "description": "A Vue component to add intersection-observer to a Vue component or HTML element.", "main": "dist/index.js", "repository": "git@github.com:heavyy/vue-intersect.git", "author": "Heavyy ", "license": "MIT", "scripts": { "test": "./node_modules/.bin/jest --coverage --coverageDirectory coverage", "lint": "./node_modules/.bin/eslint -c ./.eslintrc.js src/**/*.js", "lint:fix": "./node_modules/.bin/eslint -c ./.eslintrc.js src/**/*.js --fix", "build": "./node_modules/.bin/babel ./src -d ./dist/" }, "peerDependencies": { "vue": "^2.4.2" }, "devDependencies": { "vue": "^2.4.2", "babel-cli": "^6.24.1", "babel-core": "^6.25.0", "babel-eslint": "^7.2.3", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-env": "^1.6.0", "coveralls": "^2.13.1", "eslint": "^4.4.1", "eslint-config-standard": "^10.2.1", "eslint-plugin-import": "^2.7.0", "eslint-plugin-node": "^5.1.1", "eslint-plugin-promise": "^3.5.0", "eslint-plugin-standard": "^3.0.1", "jest": "^20.0.4" } } ================================================ FILE: src/index.js ================================================ import Vue from 'vue' const warn = (msg) => { if (!Vue.config.silent) { console.warn(msg) } } export default { name: 'intersect', abstract: true, props: { threshold: { type: Array, required: false, default: () => [0, 0.2] }, root: { type: typeof HTMLElement !== 'undefined' ? HTMLElement : Object, required: false, default: () => null }, rootMargin: { type: String, required: false, default: () => '0px 0px 0px 0px' } }, mounted () { this.observer = new IntersectionObserver((entries) => { if (!entries[0].isIntersecting) { this.$emit('leave', [entries[0]]) } else { this.$emit('enter', [entries[0]]) } this.$emit('change', [entries[0]]) }, { threshold: this.threshold, root: this.root, rootMargin: this.rootMargin }) this.$nextTick(() => { if (this.$slots.default && this.$slots.default.length > 1) { warn('[VueIntersect] You may only wrap one element in a component.') } else if (!this.$slots.default || this.$slots.default.length < 1) { warn('[VueIntersect] You must have one child inside a component.') return } this.observer.observe(this.$slots.default[0].elm) }) }, destroyed () { this.$emit('destroyed') this.observer.disconnect() }, render () { return this.$slots.default ? this.$slots.default[0] : null } }