├── diagrams ├── inview.png └── inview.xml ├── .travis.yml ├── .editorconfig ├── CHANGELOG.md ├── demo ├── demo.css ├── index.html └── demo.js ├── package.json ├── LICENSE ├── .gitignore ├── README.md └── inview.js /diagrams/inview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miguelmota/inview/HEAD/diagrams/inview.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "10" 5 | 6 | before_script: 7 | - npm install 8 | 9 | script: 10 | - npm test 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | # use line feed 8 | end_of_line = lf 9 | 10 | # ensure file ends with a newline when saving 11 | insert_final_newline = true 12 | 13 | # soft tabs 14 | indent_style = space 15 | 16 | # number of columns used for each indentation level 17 | indent_size = 2 18 | 19 | # remove any whitespace characters preceding newline characters 20 | trim_trailing_whitespace = true 21 | 22 | # character set 23 | charset = utf-8 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [Unreleased][unreleased] 6 | ### Fixed 7 | - Fix Markdown links to tag comparison URL with footnote-style links. 8 | 9 | ## [0.0.2] - 2015-02-15 10 | ### Changed 11 | - `Inview` global to `inView` 12 | 13 | ## [0.0.3] - 2015-02-22 14 | ### Changed 15 | - Fixed exports typo 16 | 17 | ## [0.0.4] - 2015-05-01 18 | ### Changed 19 | - Reverted `inview` global to `InView` 20 | - Added `data.elementOffsetTopInViewHeight` 21 | 22 | ## [0.0.5] - 2015-05-01 23 | ### Changed 24 | - Fixed inverted height values 25 | -------------------------------------------------------------------------------- /demo/demo.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | .item { 7 | border: 1px solid #ff0000; 8 | padding: 40px; 9 | font-family: monospace; 10 | font-size: 12px; 11 | } 12 | 13 | .item.inview { 14 | border: 1px solid #00ff00 15 | } 16 | 17 | .space-filler { 18 | height: 2000px; 19 | } 20 | 21 | .status { 22 | position: fixed; 23 | background: #eee; 24 | color: #000; 25 | font: monospace; 26 | font-size: 14px; 27 | padding: 5px; 28 | width: 100%; 29 | } 30 | 31 | .output { 32 | position: fixed; 33 | color: #000; 34 | font: monospace; 35 | font-size: 10px; 36 | top: 60px; 37 | left: 60px; 38 | } 39 | 40 | .success { 41 | color: #00ff00; 42 | } 43 | 44 | .fail { 45 | color: #ff0000; 46 | } 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inview", 3 | "version": "0.0.15", 4 | "description": "Detect when element scrolled to view ", 5 | "main": "inview.js", 6 | "devDependencies": { 7 | "jsdoc": "^3.3.0-alpha9", 8 | "jsdoc-oblivion": "^0.0.3", 9 | "standard": "^12.0.1" 10 | }, 11 | "scripts": { 12 | "test": "echo 'TODO'", 13 | "lint": "standard --fix inview.js", 14 | "docs": "jsdoc -c config/conf.json -d docs/ -t node_modules/jsdoc-oblivion/template/ inview.js README.md" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/miguelmota/inview.git" 19 | }, 20 | "keywords": [ 21 | "inview" 22 | ], 23 | "author": { 24 | "name": "Miguel Mota", 25 | "email": "hello@miguelmota.com", 26 | "url": "https://miguelmota.com" 27 | }, 28 | "license": { 29 | "type": "MIT", 30 | "url": "https://github.com/miguelmota/inview/blob/master/LICENSE" 31 | }, 32 | "bugs": { 33 | "url": "https://github.com/miguelmota/inview/issues" 34 | }, 35 | "homepage": "https://github.com/miguelmota/inview" 36 | } 37 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | inview demo 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 |
item
15 | 16 |
17 | 18 |

19 | 
20 |     
21 |     
22 | 
23 |    Fork me on GitHub
24 |   
25 | 
26 | 
27 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT license
 2 | 
 3 | Copyright (C) 2014 Miguel Mota
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
 6 | this software and associated documentation files (the "Software"), to deal in
 7 | the Software without restriction, including without limitation the rights to
 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | 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 | 


--------------------------------------------------------------------------------
/demo/demo.js:
--------------------------------------------------------------------------------
 1 | ;(function () {
 2 |   var el = document.querySelector('.item')
 3 |   var status = document.querySelector('.status')
 4 |   var output = document.querySelector('.output')
 5 | 
 6 |   function text (el, content) {
 7 |     el.innerText = content
 8 |   }
 9 | 
10 |   function html (el, content) {
11 |     el.innerHTML = content
12 |   }
13 | 
14 |   function statusHtml (content) {
15 |     html(status, content)
16 |   }
17 | 
18 |   function log (data) {
19 |     output.innerHTML = JSON.stringify(data, null, 2)
20 |   }
21 | 
22 |   var inView = InView(el, function (isInView, data) {
23 |     log(data)
24 |     el.textContent = data.elementOffsetTopInViewHeight
25 |     if (isInView) {
26 |       if (data.elementOffsetTopInViewHeight < data.inViewHeight / 2) {
27 |         statusHtml('status: in view (top half)')
28 |       } else {
29 |         statusHtml('status: in view (bottom half)')
30 |       }
31 |     } else {
32 |       if (data.windowScrollTop - (data.elementOffsetTop - data.inViewHeight) > data.inViewHeight) {
33 |         statusHtml('status: not in view (scroll up)')
34 |       } else {
35 |         statusHtml('status: not in view (scroll down)')
36 |       }
37 |     }
38 |   })
39 | })();
40 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
  1 | //this will affect all the git repos
  2 | git config --global core.excludesfile ~/.gitignore
  3 | 
  4 | 
  5 | //update files since .ignore won't if already tracked
  6 | git rm --cached 
  7 | 
  8 | # Compiled source #
  9 | ###################
 10 | *.com
 11 | *.class
 12 | *.dll
 13 | *.exe
 14 | *.o
 15 | *.so
 16 | 
 17 | # Packages #
 18 | ############
 19 | # it's better to unpack these files and commit the raw source
 20 | # git has its own built in compression methods
 21 | *.7z
 22 | *.dmg
 23 | *.gz
 24 | *.iso
 25 | *.jar
 26 | *.rar
 27 | *.tar
 28 | *.zip
 29 | 
 30 | # Logs and databases #
 31 | ######################
 32 | *.log
 33 | *.sql
 34 | *.sqlite
 35 | 
 36 | # OS generated files #
 37 | ######################
 38 | .DS_Store
 39 | .DS_Store?
 40 | ._*
 41 | .Spotlight-V100
 42 | .Trashes
 43 | # Icon?
 44 | ehthumbs.db
 45 | Thumbs.db
 46 | .cache
 47 | .project
 48 | .settings
 49 | .tmproj
 50 | *.esproj
 51 | nbproject
 52 | 
 53 | # Numerous always-ignore extensions #
 54 | #####################################
 55 | *.diff
 56 | *.err
 57 | *.orig
 58 | *.rej
 59 | *.swn
 60 | *.swo
 61 | *.swp
 62 | *.vi
 63 | *~
 64 | *.sass-cache
 65 | *.grunt
 66 | *.tmp
 67 | 
 68 | # Dreamweaver added files #
 69 | ###########################
 70 | _notes
 71 | dwsync.xml
 72 | 
 73 | # Komodo #
 74 | ###########################
 75 | *.komodoproject
 76 | .komodotools
 77 | 
 78 | # Node #
 79 | #####################
 80 | node_modules
 81 | 
 82 | # Bower #
 83 | #####################
 84 | bower_components
 85 | 
 86 | # Folders to ignore #
 87 | #####################
 88 | .hg
 89 | .svn
 90 | .CVS
 91 | intermediate
 92 | publish
 93 | .idea
 94 | .graphics
 95 | _test
 96 | _archive
 97 | uploads
 98 | tmp
 99 | 
100 | # Vim files to ignore #
101 | #######################
102 | .VimballRecord
103 | .netrwhist
104 | 


--------------------------------------------------------------------------------
/diagrams/inview.xml:
--------------------------------------------------------------------------------
1 | 
2 | 7Vtdc5s4FP01fmwGSQjDY+M03e7stpmmm272jRrF1i4gD5ZjJ79+JRA2SErAGOwkrR8y6CIu4pz7KcgITZLNxyxczP9kEYlH0Ik2I3QxgjDwx+KvFDwUAhcrwSyjUSECO8E1fSRK6CjpikZkWZvIGYs5XdSFU5amZMprsjDL2Lo+7Y7F9bsuwhkxBNfTMDal32nE54XUx85O/huhs3l5Z+CoM0lYTlaC5TyM2LoiQh9GaJIxxoujZDMhscSuxKW47vKJs9uFZSTlbS7452byr/f55q/HJL26yND3G+fm6p3Sch/GK/XAarH8oUQgY6s0IlKJM0Ln6znl5HoRTuXZtaBcyOY8icUIiMMlz9h/ZMJiluVXoyD/iTPmcst7k4yTTUWklv+RsITw7EFMUWeRh4tLlC2BEtr1jhlY4j+vsAI9JQyVNcy2uneAiQOF2R74wZ7xu6NxXKKXspTYIHXE7/KyJ0ixBim2QAotkG4n9g4pGtokJXqO8xzYpaeDYexWLKGl3Q6FsWtgfE/JesEybmAtnpE/B6gCzoJlGNNZKoZTgRwR8nOJGBWh9b06kdAokrexMljnuAcWtkFga+q+wYKHjkgCNkjIk9HbJcDXCChVVAhw4REJGBsEkJgk8gH75yAmd/zkDLhucyByjxmHQIv6g6TRe1nI7TCt0BCFy3mOUMFJmPFyLluQtJRdUrmsHEWhrTJ6ElMS1cpCE9EKYtiCWCnLSBxyel8vJm0wqjtcMSrNb5s5fLdOGAzOcF3Jkq2yKVHXVcu/BlUAu7oqAdWMcENVzuz20Q8g2zPI/ib9qtnZMrKkj+GPfIIcL+QS80Xj8xG+kG624mxZNA+g2es4k+61FN5G09k3ObgQKVH4L0u55r8Ju1c3dnKP5CGvjEWzQ6pjEtHqUDUlFUnK/gh/kPK5enBp7Hp1Wn3UyqXHh3u09+V3FvhL1/96O17dfoVJ+IlbGwovliQsMpHbvBnPn7sQTUW3WMpU6P1yd7ckXHDyKb0RJYnqrdR8saLqJRVxTfkLTqAGsRb6n+QaAC2BBpY6EgyUQa1sm3XkfuHb4nACi+zhb3U+H9xKBEWsUsOLzTaUy9FD6YrVTCAHVyEXpKW5BDrulgIttFv9r4iETWZ+uqygpXFH47dtThijuiLkaor6ywhWFM0CzDCf/bpn05y0TNCDF2J9FyLAhhcCC/loKCcsb9ZPEWV3necc9Z1z5jiw7q2OCxr8NR9dkYwKEGRsLYQbyre+L44rri9GO01yUCpq79WFWzxToWDT+5/eNTuV90N9yybo6P3I0RQd2fv7Lf472q1Xt1sAgldot25Lu4Untduxlmz0qqS13UIt/Y2PbLewT7u1WGjXPrbZlk7FPS67kpIy0JF7XZGxDT0097bd6Vb9zZqmEVtfTzMWx7LlfAMtjcV0e6ivPKC/kgjM+qqcc5QuBxza5hgOfxkmNJbPNxF2TwUP0PlM1uqkegkKYEP+2maj9g3SsD0ROmmICbTm2Au6ppe6IvfY6cXcJjvA2mq5pJpfXm8ucfGZVgBAr2M2MVQBiI5LdosW+HihxR4iugacPkOLf1KLw/WIAAHqZm8ealA0tLX5+1Qve27Y7lnR/KpzgL6LZ+7cA3jMOgf2upH09jKP62juG3R8F6crAniwN3F2og/deTk00bx0plGA+2K6QdHQTB+6V/GzMQ2w2w/ThqKhmW7x3dxPzTR2QZ0gvdRv3TXgBkVDM93HW9e3zbSWpzv3h3rhrysammnzE8Hu9Trt/kHFK6rVR4cX5lDbAIJj8ztFMNTnunY7OHRL6NV+Z1F4wIsp+H2N4daNv+fXFLlI25wcOpAcus301lMGGgf1nN61DER+g6KhmbZt8fxiupLTx75GEOjGNPYbFHVmWgx3/xZWTN/9bx368D8=


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | 

2 |
3 | logo 4 |
5 |
6 |
7 |

8 | 9 | # inView 10 | 11 | > Detect when element scrolled to view 12 | 13 | [![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/miguelmota/inview/master/LICENSE) [![Build Status](https://travis-ci.org/miguelmota/inview.svg?branch=master)](https://travis-ci.org/miguelmota/inview) [![dependencies Status](https://david-dm.org/miguelmota/inview/status.svg)](https://david-dm.org/miguelmota/inview) [![NPM version](https://badge.fury.io/js/inview.svg)](http://badge.fury.io/js/inview) 14 | 15 | ## Demo 16 | 17 | [https://lab.miguelmota.com/inview/demo/](https://lab.miguelmota.com/inview/demo/) 18 | 19 | ## Install 20 | 21 | ```bash 22 | npm install inview 23 | ``` 24 | 25 | ## Getting started 26 | 27 | Basic example 28 | 29 | ```javascript 30 | var inview = InView(el, function(isInView) { 31 | if (isInView) { 32 | // do something 33 | // ... 34 | } 35 | }); 36 | ``` 37 | 38 | Example showing if visible top half or bottom half of screen 39 | 40 | ```javascript 41 | var inview = InView(el, function(isInView, data) { 42 | if (isInView) { 43 | if (data.elementOffsetTopInViewHeight < data.inViewHeight/2) { 44 | console.log('in view (top half)') 45 | } else { 46 | console.log('in view (bottom half)') 47 | } 48 | } else { 49 | if (data.windowScrollTop - (data.elementOffsetTop - data.inViewHeight) > data.inViewHeight) { 50 | console.log('not in view (scroll up)') 51 | } else { 52 | console.log('not in view (scroll down)') 53 | } 54 | } 55 | }) 56 | ``` 57 | 58 | Destroy InView listeners 59 | 60 | ```javascript 61 | var inview = InView(el, function(isInView, data) { 62 | if (isInView) { 63 | // do something 64 | // ... 65 | 66 | this.destroy() 67 | } 68 | }) 69 | 70 | // another way 71 | inview.destroy() 72 | ``` 73 | 74 | ## Documentation 75 | 76 | Constructor: 77 | 78 | - *InView(element, callback)* 79 | 80 | Scroll callback parameters: 81 | 82 | - *{boolean} isInView* - is in view 83 | - *{object} data* - scroll data 84 | - *{number} data.windowScrollTop* - scrolled amount 85 | - *{number} data.elementOffsetTop* - element top offset 86 | - *{number} data.inViewHeight* - height of visible area 87 | - *{number} data.elementOffsetTopInViewHeight* - element top offset relative to height of visible area 88 | 89 | Visualization: 90 | 91 | diagram 92 | 93 | ## License 94 | 95 | [MIT](LICENSE) 96 | -------------------------------------------------------------------------------- /inview.js: -------------------------------------------------------------------------------- 1 | ;(function (root) { 2 | function throttle (fn, threshhold, scope) { 3 | threshhold || (threshhold = 100) 4 | var last, 5 | deferTimer 6 | 7 | return function () { 8 | var context = scope || this 9 | 10 | var now = +(new Date()) 11 | 12 | var args = arguments 13 | if (last && now < last + threshhold) { 14 | clearTimeout(deferTimer) 15 | deferTimer = setTimeout(function () { 16 | last = now 17 | fn.apply(context, args) 18 | }, threshhold) 19 | } else { 20 | last = now 21 | fn.apply(context, args) 22 | } 23 | } 24 | } 25 | 26 | function hasClass (el, name) { 27 | return new RegExp(' ' + name + ' ').test(' ' + el.className + ' ') 28 | } 29 | 30 | function addClass (el, name) { 31 | if (!hasClass(el, name)) { 32 | el.className += ' ' + name 33 | } 34 | return el 35 | } 36 | 37 | function removeClass (el, name) { 38 | var newClass = ' ' + el.className.replace(/[\t\r\n]/g, ' ') + ' ' 39 | if (hasClass(el, name)) { 40 | while (newClass.indexOf(' ' + name + ' ') >= 0) { 41 | newClass = newClass.replace(' ' + name + ' ', ' ') 42 | } 43 | el.className = newClass.replace(/^\s+|\s+$/g, '') 44 | } 45 | return el 46 | } 47 | 48 | function removeEvent (el, name, fn) { 49 | if (el.removeEventListener) { 50 | return el.removeEventListener(name, fn) 51 | } else if (el.detachEvent) { 52 | return el.detachEvent('on' + name, fn) 53 | } 54 | } 55 | 56 | function addEvent (el, name, fn) { 57 | if (el.addEventListener) { 58 | return el.addEventListener(name, fn, false) 59 | } else if (el.attachEvent) { 60 | return el.attachEvent('on' + name, fn) 61 | } 62 | } 63 | 64 | function getScrollTop () { 65 | if (typeof window.pageYOffset !== 'undefined') { 66 | return window.pageYOffset 67 | } else { 68 | var b = document.body 69 | var d = document.documentElement 70 | d = d.clientHeight ? d : b 71 | return d.scrollTop 72 | } 73 | } 74 | 75 | function isInView (obj) { 76 | var winTop = getScrollTop() 77 | 78 | var winBottom = winTop + window.innerHeight 79 | 80 | var objTop = obj.getBoundingClientRect().top + document.documentElement.scrollTop 81 | 82 | var objBottom = objTop + obj.offsetHeight 83 | 84 | var offset = 0 85 | 86 | if ((objTop <= winBottom + offset) && (objBottom >= winTop)) { 87 | return true 88 | } 89 | 90 | return false 91 | } 92 | 93 | /** 94 | * @desc Create an InView instance. 95 | * 96 | * @class 97 | * @func InView 98 | * @param {HTMLElement} element - element to detect when scrolled to view 99 | * @param {scrollCallback} scrollCallback - callback function fired on scroll event 100 | * @return {HTMLElement} - element 101 | * 102 | * @example 103 | * var el = document.querySelector('.item'); 104 | * 105 | * var InView = InView(el, function(isInView, data) { 106 | * if (isInView) { 107 | * console.log('in view'); 108 | * } else { 109 | * if (data.windowScrollTop - (data.elementOffsetTop - data.inViewHeight) > data.inViewHeight) { 110 | * console.log('not in view (scroll up)'); 111 | * } else { 112 | * console.log('not in view (scroll down)'); 113 | * } 114 | * } 115 | * }); 116 | */ 117 | function InView (el, callback) { 118 | var _this = this 119 | if (!(_this instanceof InView)) { 120 | return new InView(el, callback) 121 | } 122 | 123 | _this.el = el 124 | _this.callback = callback.bind(_this) 125 | _this.destroy = function () {} 126 | 127 | if (!el) { 128 | return _this 129 | } 130 | 131 | var isDestroyed = false 132 | 133 | var check = function check (e) { 134 | if (isDestroyed) return false 135 | 136 | var params = { 137 | windowScrollTop: getScrollTop(), 138 | elementOffsetTop: _this.el.offsetTop, 139 | inViewHeight: window.innerHeight, 140 | elementOffsetTopInViewHeight: window.innerHeight - (getScrollTop() - (_this.el.offsetTop - window.innerHeight)) 141 | } 142 | if (isInView(_this.el)) { 143 | addClass(_this.el, 'inview') 144 | _this.callback.call(_this, true, params) 145 | } else { 146 | removeClass(_this.el, 'inview') 147 | _this.callback.call(_this, false, params) 148 | } 149 | } 150 | 151 | var throttledCheck = throttle(check, 100) 152 | 153 | addEvent(window, 'scroll', throttledCheck) 154 | 155 | _this.destroy = function () { 156 | isDestroyed = true 157 | removeEvent(window, 'scroll', throttledCheck) 158 | } 159 | 160 | throttledCheck() 161 | 162 | return _this 163 | } 164 | 165 | /** 166 | * @desc InView callback 167 | * 168 | * @callback scrollCallback 169 | * @param {boolean} isInView - is in view 170 | * @param {object} data - scroll data 171 | * @param {number} data.windowScrollTop - scrolled amount 172 | * @param {number} data.elementOffsetTop - element top offset 173 | * @param {number} data.inViewHeight - height of visible area 174 | * @param {number} data.elementOffsetTopInViewHeight - element top offset relative to height of visible area 175 | */ 176 | 177 | if (typeof exports !== 'undefined') { 178 | if (typeof module !== 'undefined' && module.exports) { 179 | exports = module.exports = InView 180 | } 181 | exports.InView = InView 182 | } else { 183 | root.InView = InView 184 | } 185 | })(window); 186 | --------------------------------------------------------------------------------