├── .nvmrc ├── .husky ├── pre-commit └── commit-msg ├── src ├── main.ts ├── components │ └── Waypoint │ │ ├── index.ts │ │ ├── observer.ts │ │ └── component.ts ├── env.d.ts ├── __tests__ │ ├── ssr.spec.ts │ └── example.spec.ts └── App.vue ├── tsconfig.vitest.json ├── tsconfig.config.json ├── tsconfig.json ├── tsconfig.app.json ├── .gitignore ├── index.html ├── vite.config.ts ├── .eslintrc.cjs ├── vite.config.lib.ts ├── .github └── workflows │ ├── release.yml │ └── pr.yml ├── LICENSE ├── package.json ├── CHANGELOG.md └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit $1 5 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | 4 | createApp(App).mount("#app"); 5 | -------------------------------------------------------------------------------- /src/components/Waypoint/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Waypoint } from "./component"; 2 | export { Going, Direction, type WaypointState } from "./observer"; 3 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module "*.vue" { 4 | import type { DefineComponent } from "vue"; 5 | const component: DefineComponent; 6 | export default component; 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.vitest.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.app.json", 3 | "exclude": [], 4 | "compilerOptions": { 5 | "composite": true, 6 | "lib": [], 7 | "types": ["node", "jsdom"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.node.json", 3 | "include": ["vite.config.*", "vitest.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.config.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | }, 10 | { 11 | "path": "./tsconfig.vitest.json" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": ["src/env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "exclude": ["src/**/__tests__/*"], 5 | "compilerOptions": { 6 | "composite": true, 7 | "baseUrl": ".", 8 | "paths": { 9 | "@/*": ["./src/*"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.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 | # Local Netlify folder 26 | .netlify -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from "node:url"; 2 | import { defineConfig } from "vite"; 3 | import vue from "@vitejs/plugin-vue"; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [vue()], 8 | resolve: { 9 | alias: { 10 | "@": fileURLToPath(new URL("./src", import.meta.url)), 11 | }, 12 | }, 13 | server: { 14 | host: true, 15 | port: 3000, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /src/__tests__/ssr.spec.ts: -------------------------------------------------------------------------------- 1 | // @vitest-environment jsdom 2 | 3 | import { describe, it, expect } from "vitest"; 4 | import { renderToString } from "@vue/test-utils"; 5 | import { Waypoint } from "@/components/Waypoint"; 6 | 7 | describe("Template SSR output", () => { 8 | it("renders the component in SSR contexts without erroring out while printing slot", async () => { 9 | const contents = await renderToString(Waypoint, { 10 | slots: { default: "Test" }, 11 | }); 12 | expect(contents).toBe('
Test
'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require("@rushstack/eslint-patch/modern-module-resolution"); 3 | 4 | module.exports = { 5 | root: true, 6 | extends: [ 7 | "plugin:vue/vue3-essential", 8 | "eslint:recommended", 9 | "@vue/eslint-config-typescript/recommended", 10 | "@vue/eslint-config-prettier", 11 | ], 12 | parserOptions: { 13 | ecmaVersion: "latest", 14 | }, 15 | rules: { 16 | "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", 17 | "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /vite.config.lib.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import { defineConfig, mergeConfig } from "vite"; 3 | import baseConfig from "./vite.config"; 4 | 5 | const libConfig = defineConfig({ 6 | build: { 7 | lib: { 8 | entry: resolve(__dirname, "src/components/Waypoint/index.ts"), 9 | name: "VueWaypoint", 10 | fileName: "vue-waypoint", 11 | }, 12 | rollupOptions: { 13 | external: ["vue"], 14 | output: { 15 | globals: { 16 | vue: "Vue", 17 | }, 18 | }, 19 | }, 20 | }, 21 | }); 22 | 23 | export default mergeConfig(baseConfig, libConfig); 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | cancel: 10 | name: Cancel Previous Runs 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 3 13 | steps: 14 | - uses: styfle/cancel-workflow-action@0.4.0 15 | with: 16 | access_token: ${{ github.token }} 17 | 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | # Setup .npmrc file to publish to npm 23 | - uses: actions/setup-node@v1 24 | with: 25 | node-version: "16.x.x" 26 | registry-url: "https://registry.npmjs.org" 27 | - run: npm ci 28 | - run: npm run build-only-lib 29 | - run: npm publish 30 | env: 31 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | cancel: 10 | name: Cancel Previous Runs 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 3 13 | steps: 14 | - uses: styfle/cancel-workflow-action@0.4.0 15 | with: 16 | access_token: ${{ github.token }} 17 | 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | # Setup .npmrc file to publish to npm 23 | - uses: actions/setup-node@v1 24 | with: 25 | node-version: "16.x.x" 26 | registry-url: "https://registry.npmjs.org" 27 | - run: npm ci 28 | - run: npm run type-check 29 | - run: npm run lint 30 | - run: npm run test:unit 31 | - run: npm run build-only-lib 32 | -------------------------------------------------------------------------------- /src/__tests__/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { beforeEach, describe, it, expect, vi } from "vitest"; 2 | import { shallowMount } from "@vue/test-utils"; 3 | import { Waypoint } from "@/components/Waypoint"; 4 | 5 | beforeEach(() => { 6 | const noop = () => null; 7 | const mockIntersectionObserver = vi.fn(); 8 | mockIntersectionObserver.mockReturnValue({ observe: noop, unobserve: noop }); 9 | window.IntersectionObserver = mockIntersectionObserver; 10 | }); 11 | 12 | describe("Template output", () => { 13 | it("renders the component with div tag", () => { 14 | const wrapper = shallowMount(Waypoint); 15 | expect(wrapper.html()).toMatch(`
`); 16 | }); 17 | 18 | it("renders the component with passed tag", () => { 19 | const wrapper = shallowMount(Waypoint, { props: { tag: "span" } }); 20 | expect(wrapper.html()).toMatch(``); 21 | }); 22 | 23 | it("renders the component with passed tag", () => { 24 | const wrapper = shallowMount(Waypoint, { props: { tag: "p" } }); 25 | expect(wrapper.html()).toMatch(`

`); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Marco 'Gatto' Boffo 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. -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 35 | 36 | 63 | -------------------------------------------------------------------------------- /src/components/Waypoint/observer.ts: -------------------------------------------------------------------------------- 1 | import { ref } from "vue"; 2 | 3 | export type WaypointState = { 4 | el: Element | undefined; 5 | going: Going | undefined; 6 | direction: Direction | undefined; 7 | }; 8 | 9 | export enum Going { 10 | In = "IN", 11 | Out = "OUT", 12 | } 13 | 14 | export enum Direction { 15 | Up = "UP", 16 | Down = "DOWN", 17 | Left = "LEFT", 18 | Right = "RIGHT", 19 | } 20 | 21 | const toGoing = (isIntersecting: boolean): Going => { 22 | return isIntersecting ? Going.In : Going.Out; 23 | }; 24 | 25 | const toDirection = ( 26 | rect: DOMRectReadOnly, 27 | oldRect: DOMRectReadOnly 28 | ): Direction | undefined => { 29 | if (rect.top < oldRect.top) return Direction.Up; 30 | if (rect.left > oldRect.left) return Direction.Right; 31 | if (rect.top > oldRect.top) return Direction.Down; 32 | if (rect.left < oldRect.left) return Direction.Left; 33 | }; 34 | 35 | type Callback = (state: WaypointState) => void; 36 | 37 | export const createObserver = (options?: IntersectionObserverInit) => { 38 | return (callback: Callback) => { 39 | const boundingClientRect = ref(); 40 | 41 | return new window.IntersectionObserver(([entry]) => { 42 | // this should never happen 43 | if (typeof entry === "undefined") { 44 | console.error("[vue-waypoint]", "observed element is undefined"); 45 | return; 46 | } 47 | 48 | // set the default bounding client 49 | // this happens only on the first call 50 | boundingClientRect.value ??= entry.boundingClientRect; 51 | 52 | // create a new state and notify 53 | callback({ 54 | el: entry.target, 55 | going: toGoing(entry.isIntersecting), 56 | direction: toDirection( 57 | entry.boundingClientRect, 58 | boundingClientRect.value 59 | ), 60 | }); 61 | 62 | // save the rect for next matching 63 | boundingClientRect.value = entry.boundingClientRect; 64 | }, options); 65 | }; 66 | }; 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-waypoint", 3 | "version": "4.3.0", 4 | "license": "MIT", 5 | "private": false, 6 | "main": "dist/vue-waypoint.umd.js", 7 | "module": "dist/vue-waypoint.mjs", 8 | "typings": "src/components/Waypoint/index.ts", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/scaccogatto/vue-waypoint" 12 | }, 13 | "scripts": { 14 | "dev": "vite", 15 | "build": "run-p type-check build-only", 16 | "test:unit": "vitest --environment jsdom", 17 | "build-only-lib": "vite --config vite.config.lib.ts build", 18 | "build-only": "vite build", 19 | "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false", 20 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore", 21 | "release": "standard-version", 22 | "prepare": "husky install" 23 | }, 24 | "devDependencies": { 25 | "@commitlint/cli": "^17.0.3", 26 | "@commitlint/config-conventional": "^17.0.3", 27 | "@rushstack/eslint-patch": "^1.1.4", 28 | "@types/jsdom": "^20.0.0", 29 | "@types/node": "^16.11.47", 30 | "@vitejs/plugin-vue": "^3.0.1", 31 | "@vue/eslint-config-prettier": "^7.0.0", 32 | "@vue/eslint-config-typescript": "^11.0.0", 33 | "@vue/test-utils": "^2.3.0", 34 | "@vue/tsconfig": "^0.1.3", 35 | "eslint": "^8.21.0", 36 | "eslint-plugin-vue": "^9.3.0", 37 | "husky": "^8.0.1", 38 | "jsdom": "^20.0.0", 39 | "lint-staged": "^13.0.3", 40 | "npm-run-all": "^4.1.5", 41 | "prettier": "^2.7.1", 42 | "standard-version": "^9.5.0", 43 | "typescript": "~4.7.4", 44 | "vite": "^3.0.4", 45 | "vitest": "^0.21.0", 46 | "vue": "^3.2.37", 47 | "vue-tsc": "^0.39.5" 48 | }, 49 | "peerDependencies": { 50 | "vue": "^3.0.0" 51 | }, 52 | "lint-staged": { 53 | "*": "prettier --write --ignore-path .gitignore --ignore-unknown", 54 | "*.{vue,js,jsx,cjs,mjs,ts,tsx,cts,mts}": "eslint --fix --ignore-path .gitignore" 55 | }, 56 | "commitlint": { 57 | "extends": [ 58 | "@commitlint/config-conventional" 59 | ] 60 | }, 61 | "volta": { 62 | "node": "16.20.2" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/Waypoint/component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | computed, 3 | defineComponent, 4 | h, 5 | onBeforeUnmount, 6 | onMounted, 7 | ref, 8 | watch, 9 | type PropType, 10 | } from "vue"; 11 | import { createObserver, type WaypointState } from "./observer"; 12 | 13 | export default defineComponent({ 14 | // eslint-disable-next-line vue/multi-word-component-names 15 | name: "Waypoint", 16 | props: { 17 | active: { 18 | type: Boolean, 19 | default: () => true, 20 | }, 21 | options: { 22 | type: Object as PropType, 23 | default: () => ({}), 24 | }, 25 | tag: { 26 | type: String, 27 | default: () => "div", 28 | }, 29 | disableCssHelpers: { 30 | type: Boolean, 31 | default: () => false, 32 | }, 33 | }, 34 | setup(props, context) { 35 | // check for mounted status 36 | const mounted = ref(false); 37 | 38 | // element DOM reference 39 | const element = ref(null); 40 | 41 | // activatable conditions 42 | const activatable = computed( 43 | () => mounted.value && props.active && element.value !== null 44 | ); 45 | 46 | const waypointState = ref(); 47 | const updateWaypointState = (newState: WaypointState) => 48 | (waypointState.value = newState); 49 | 50 | const observer = ref(); 51 | watch(activatable, () => { 52 | // cannot observer or unobserve if the element is null 53 | if (element.value === null) return; 54 | 55 | if (activatable.value && observer.value) 56 | return observer.value.observe(element.value); 57 | else return observer.value?.unobserve(element.value); 58 | }); 59 | 60 | watch(waypointState, () => { 61 | if (typeof waypointState.value === "undefined") return; 62 | context.emit("change", waypointState.value); 63 | }); 64 | 65 | // bind and unbind IntersectionObserver as needed 66 | onMounted(() => { 67 | mounted.value = true; 68 | observer.value = createObserver(props.options)(updateWaypointState); 69 | }); 70 | 71 | onBeforeUnmount(() => (mounted.value = false)); 72 | 73 | const cssHelpers = computed(() => { 74 | const { going, direction: dir } = waypointState.value ?? {}; 75 | const goingClass = going && `going-${going.toLowerCase()}`; 76 | const directionClass = dir && `direction-${dir.toLowerCase()}`; 77 | return ["waypoint", goingClass, directionClass]; 78 | }); 79 | 80 | return () => { 81 | const rawProps = props.disableCssHelpers 82 | ? { ref: element } 83 | : { ref: element, class: cssHelpers.value }; 84 | 85 | return h( 86 | props.tag, 87 | rawProps, 88 | context.slots.default?.(waypointState.value ?? {}) 89 | ); 90 | }; 91 | }, 92 | }); 93 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [4.3.0](https://github.com/scaccogatto/vue-waypoint/compare/v4.2.5...v4.3.0) (2023-08-28) 6 | 7 | ### Features 8 | 9 | - added support for SSR environments ([#77](https://github.com/scaccogatto/vue-waypoint/issues/77)) ([1ae537b](https://github.com/scaccogatto/vue-waypoint/commit/1ae537bbfa86d6096b6c45e47d65a704ba833f4a)) 10 | 11 | ### [4.2.5](https://github.com/scaccogatto/vue-waypoint/compare/v4.2.4...v4.2.5) (2022-09-12) 12 | 13 | ### Bug Fixes 14 | 15 | - typing export ([612fd4c](https://github.com/scaccogatto/vue-waypoint/commit/612fd4c8e4c6efdbdc42a06a3c1c5a47ad7a0b49)) 16 | 17 | ### [4.2.4](https://github.com/scaccogatto/vue-waypoint/compare/v4.2.3...v4.2.4) (2022-08-15) 18 | 19 | ### [4.2.3](https://github.com/scaccogatto/vue-waypoint/compare/v4.2.2...v4.2.3) (2022-08-13) 20 | 21 | ### Bug Fixes 22 | 23 | - node version on build ([f5944df](https://github.com/scaccogatto/vue-waypoint/commit/f5944df31e90ac6ea19a6474aa183ffc7f834814)) 24 | 25 | ### [4.2.2](https://github.com/scaccogatto/vue-waypoint/compare/v4.2.1...v4.2.2) (2022-08-13) 26 | 27 | ### Bug Fixes 28 | 29 | - intermediate states cleanup ([2766be8](https://github.com/scaccogatto/vue-waypoint/commit/2766be8d583ac546e81576e11add97879e4db0c1)) 30 | 31 | ### [4.2.1](https://github.com/scaccogatto/vue-waypoint/compare/v4.2.0...v4.2.1) (2021-09-07) 32 | 33 | ## [4.2.0](https://github.com/scaccogatto/vue-waypoint/compare/v4.1.1...v4.2.0) (2021-08-17) 34 | 35 | ### Features 36 | 37 | - disableCssHelpers ([3ff1e45](https://github.com/scaccogatto/vue-waypoint/commit/3ff1e458a3d4e519c031a0ce72e21454bff51673)) 38 | 39 | ### [4.1.1](https://github.com/scaccogatto/vue-waypoint/compare/v4.1.0...v4.1.1) (2021-08-10) 40 | 41 | ## [4.1.0](https://github.com/scaccogatto/vue-waypoint/compare/v4.0.0...v4.1.0) (2021-07-09) 42 | 43 | ### Features 44 | 45 | - adds el as example ([09cdc71](https://github.com/scaccogatto/vue-waypoint/commit/09cdc716873ac0711a2078f248aa2749d31e2629)) 46 | 47 | ## [4.0.0](https://github.com/scaccogatto/vue-waypoint/compare/v3.5.0...v4.0.0) (2021-06-21) 48 | 49 | ### ⚠ BREAKING CHANGES 50 | 51 | - vue2 support is over 52 | 53 | - feat: waypoint bind/unbind 54 | 55 | - feat: tag selection 56 | 57 | - feat: export ts types 58 | 59 | - fix: lib target 60 | 61 | - chore: remove roadmap since it is done 62 | 63 | - build: standard version 64 | 65 | - docs: generally improved and vue2 refs 66 | 67 | - build: release on tag push 68 | 69 | - chore: license and private to false 70 | 71 | - fix: aligned version 72 | 73 | - docs: dev steps 74 | 75 | - docs: move CSS helpers on top 76 | 77 | CSS helpers will probably be the most used feature, so we move it to the top 78 | 79 | - docs: CSS features list 80 | 81 | - fix: remove impossible path 82 | 83 | - feat: final decorations 84 | 85 | ### Features 86 | 87 | - vue3 support ([#46](https://github.com/scaccogatto/vue-waypoint/issues/46)) ([2fe79ee](https://github.com/scaccogatto/vue-waypoint/commit/2fe79ee0e1c30bc314b5c66fc3eadbdbca536d4f)) 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VueWaypoint 2 | 3 | > trigger functions and events based on the element position on the screen 4 | 5 | ![Latest Release](https://github.com/scaccogatto/vue-waypoint/workflows/Release/badge.svg) 6 | 7 | ## Demo 8 | 9 | [Simple demo page](https://vue-waypoint.netlify.app/) 10 | 11 | Open your browser console and see what's going on while scrolling up and down 12 | 13 | ## Features 14 | 15 | - [x] Vue 3 16 | - [x] No dependencies 17 | - [x] Flexible 18 | - [x] Typescript 19 | - [x] Battle tested 20 | - [x] Customizable 21 | - [x] Solid project (5+ years) 22 | - [x] Supports slots 23 | 24 | ## Getting started 25 | 26 | ### npm 27 | 28 | ```bash 29 | npm i vue-waypoint 30 | ``` 31 | 32 | ### Vue component 33 | 34 | ```html 35 | 40 | ``` 41 | 42 | ```html 43 | 71 | ``` 72 | 73 | ## Props 74 | 75 | ### `active` 76 | 77 | - [x] Can use a reactive variable 78 | - [x] Can set `true`/`false` dynamically 79 | 80 | Usage: 81 | 82 | - Enable the waypoint: `` 83 | - Disable the waypoint: `` 84 | 85 | ### `options` 86 | 87 | - [x] Useful for inner div detection 88 | - [x] Trigger `change` event a portion of the element is completely on screen 89 | - [x] Is an [official IntersectionObserverInit implementation](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver) 90 | 91 | Usage: 92 | 93 | - Set a custom `IntersectionObserver` options: `` 94 | - Read what you can do with `options`: [IntersectionObserverInit docs](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver) 95 | 96 | Options example: 97 | 98 | ```js 99 | const options: IntersectionObserverInit = { 100 | root: document, 101 | rootMargin: "0px 0px 0px 0px", 102 | threshold: [0.25, 0.75], 103 | }; 104 | ``` 105 | 106 | ### `tag` 107 | 108 | - [x] Set your preferred tag for the element 109 | - [x] Defaults to `div` 110 | 111 | - Waypoint as div: ` --> renders -->
` 112 | - Waypoint as span: ` --> renders --> ` 113 | - Waypoint as p: ` --> renders -->

` 114 | 115 | ### `disableCssHelpers` 116 | 117 | - [x] Disable automatic CSS classes on the Waypoint component 118 | - [x] Defaults to `false` 119 | 120 | Usage: 121 | 122 | - Enable helpers (default): `` 123 | - Disable helpers: `` 124 | 125 | DOM result: 126 | 127 | - With CSS helpers: ` --> renders -->
` 128 | - Without CSS helpers: ` --> renders -->
` 129 | 130 | ## CSS helpers 131 | 132 | - [x] Zero configuration needed 133 | - [x] Useful for simple CSS animations 134 | 135 | The component comes with three classes: 136 | 137 | - `waypoint`: set when the waypoint is ready 138 | - `going-in`, `going-out`: dynamically changed when the waypoint comes in and out 139 | - `direction-up`, `direction-down`, `direction-left`, `direction-right`: dynamically changed when the direction changes 140 | 141 | Examples: 142 | 143 | - `` - the element is visible and came from bottom and is going top (natural scroll) 144 | - `` - the element is visible and came from top and is going up (reverse natural scroll) 145 | - `` - the element is not visible and came from bottom and is going top 146 | - `` - the element is not visible and came from top and is going up 147 | 148 | ## Events 149 | 150 | ### `change` 151 | 152 | Emitted every time the waypoint detects a change. 153 | 154 | ```html 155 | 158 | ``` 159 | 160 | ```js 161 | function onChange(waypointState) { 162 | /* ... */ 163 | } 164 | ``` 165 | 166 | ```js 167 | interface WaypointState { 168 | el: Element; 169 | going: "IN" | "OUT"; 170 | direction: "UP" | "DOWN" | "LEFT" | "RIGHT"; 171 | } 172 | ``` 173 | 174 | ## Development 175 | 176 | 1. Fork the repository 177 | 2. Run the project (`npm i && npm run dev`) 178 | 3. Follow [Conventional Commits spec](https://www.conventionalcommits.org/en/v1.0.0/) for your commits 179 | 4. Open a pull request 180 | 181 | ## LEGACY: Vue2 and Nuxt version 182 | 183 | [vue-waypoint for Vue2 repository](https://github.com/scaccogatto/vue-waypoint/tree/vue2) 184 | --------------------------------------------------------------------------------