Detect if an Ember View or Component is in the viewport @ 60FPS
ember-in-viewport is built and maintained by DockYard, contact us for expert Ember.js consulting.
This Ember addon adds a simple, highly performant Service or Mixin to your app. This library will allow you to check if a Component
or DOM element has entered the browser's viewport. By default, this uses the IntersectionObserver
API if it detects it the DOM element is in your user's browser – failing which, it falls back to using requestAnimationFrame
, then if not available, the Ember run loop and event listeners.
We utilize pooling techniques to reuse Intersection Observers and rAF observers in order to make your app as performant as possible and do as little works as possible.
dummy-artwork
for an example artwork component). Visit http://localhost:4200/infinity-modifier
to see it in actionember serve
): https://github.com/DockYard/ember-in-viewport/tree/master/tests/dummyember install ember-in-viewport
Usage is simple. First, inject the service to your component and start "watching" DOM elements.
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
export default class MyClass extends Component {
@service inViewport
@action
setupInViewport() {
const loader = document.getElementById('loader');
const viewportTolerance = { bottom: 200 };
const { onEnter, _onExit } = this.inViewport.watchElement(loader, { viewportTolerance });
// pass the bound method to `onEnter` or `onExit`
onEnter(this.didEnterViewport.bind(this));
}
didEnterViewport() {
// do some other stuff
this.infinityLoad();
}
willDestroy() {
// need to manage cache yourself if you don't use the mixin
const loader = document.getElementById('loader');
this.inViewport.stopWatching(loader);
super.willDestroy(...arguments);
}
}
You can also use Modifiers
as well. Using modifiers cleans up the boilerplate needed and is shown in a later example.
This addon also supports the use of a Mixin to your Component
:
import Component from '@ember/component';
import InViewportMixin from 'ember-in-viewport';
export default Component.extend(InViewportMixin, {
// ...
});
To use with the service based approach, simply pass in the options to watchElement
as the second argument.
import Component from '@ember/component';
import { inject as service } from '@ember/service';
export default class MyClass extends Component {
@service inViewport
didInsertElement() {
super.didInsertElement(...arguments);
const { onEnter, _onExit } = this.inViewport.watchElement(
loader,
{
viewportTolerance: { bottom: 200 },
intersectionThreshold: 0.25,
scrollableArea: '#scrollable-area'
}
);
}
}
The mixin comes with some options as well. Due to the way listeners and IntersectionObserver API
or requestAnimationFrame
is setup, you'll have to override the options this way:
import Component from '@ember/component';
import InViewportMixin from 'ember-in-viewport';
import { setProperties } from '@ember/object';
export default Component.extend(InViewportMixin, {
init() {
this._super(...arguments);
setProperties(this, {
viewportEnabled : true,
viewportUseRAF : true,
viewportSpy : false,
viewportScrollSensitivity : 1,
viewportRefreshRate : 150,
intersectionThreshold : 0,
scrollableArea : null,
viewportTolerance: {
top : 50,
bottom : 50,
left : 20,
right : 20
}
});
}
});
viewportEnabled: boolean
Default: true
Set to false to have no listeners registered. Useful if you have components that function with either viewport listening on or off.
viewportUseIntersectionObserver: boolean
Default: Depends on browser
Read-only
This library, by default, will use the IntersectionObserver API. If IntersectionObserver is not supported in the target browser, ember-in-viewport will fallback to rAF. We prevent users from explicitly setting this to true
as browsers lacking support for IntersectionObserver will throw an error.
(https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)(https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/thresholds#Browser_compatibility)
intersectionThreshold: decimal or array
Default: 0
A single number or array of numbers between 0.0 and 1.0. A value of 0.0 means the target will be visible when the first pixel enters the viewport. A value of 1.0 means the entire target must be visible to fire the didEnterViewport hook.Similarily, [0, .25, .5, .75, 1] will fire didEnterViewport every 25% of the target that is visible.(https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Thresholds)
Some notes:
didExitViewport
that the target is initially offscreen. Similarily, this is possible to notify if onscreen when your site loads.didExitViewport
hook fired due to our use of the isIntersecting
property. See last comment here: https://bugs.chromium.org/p/chromium/issues/detail?id=713819 for purpose of isIntersecting
didExitViewport
fire, set your intersectionThreshold
to [0, 1.0]
. When set to just 1.0
, when the element is 99% visible and still has isIntersecting as true, when the element leaves the viewport, the element isn't applicable to the observer anymore, so the callback isn't called again.didEnterViewport
and didExitViewport
at the appropriate time.scrollableArea: string | HTMLElement
Default: null
A CSS selector for the scrollable area. e.g. ".my-list"
viewportUseRAF: boolean
Default: Depends on browser
As its name suggests, if this is true
and the IntersectionObserver API is not available in the target browser, the Mixin will use requestAnimationFrame
. Unless you want to force enabling or disabling this, you won't need to override this option.
viewportSpy: boolean
Default: false
viewportSpy: true
is often useful when you have "infinite lists" that need to keep loading more data.viewportSpy: false
is often useful for one time loading of artwork, metrics, etc when the come into the viewport.
If you support IE11 and detect and run logic onExit
, then it is necessary to have this true
to that the requestAnimationFrame watching your sentinel is not torn down.
When true
, the library will continually watch the Component
and re-fire hooks whenever it enters or leaves the viewport. Because this is expensive, this behaviour is opt-in. When false, the Mixin will only watch the Component
until it enters the viewport once, and then it sets viewportEntered
to true
(permanently), and unbinds listeners. This reduces the load on the Ember run loop and your application.
NOTE: If using IntersectionObserver (default), viewportSpy wont put too much of a tax on your application. However, for browsers (Safari < 12.1) that don't currently support IntersectionObserver, we fallback to rAF. Depending on your use case, the default of false
may be acceptable.
viewportDidScroll: boolean
Default: true
When true
, the Mixin enables listening to the didScroll
hook. This will become by default false in a future major release
viewportScrollSensitivity: number
Default: 1
This value determines the degree of sensitivity (in px
) in which a DOM element is considered to have scrolled into the viewport. For example, if you set viewportScrollSensitivity
to 10
, the didScroll{...}
hooks would only fire if the scroll was greater than 10px
. Only applicable if IntersectionObserver and rAF are not applied.
viewportRefreshRate: number
Default: 100
If IntersectionObserver
and requestAnimationFrame
is not present, this value determines how often the Mixin checks your component to determine whether or not it has entered or left the viewport. The lower this number, the more often it checks, and the more load is placed on your application. Generally, you'll want this value between 100
to 300
, which is about the range at which people consider things to be "real-time".
This value also affects how often the Mixin checks scroll direction.
viewportTolerance: object
Default: { top: 0, left: 0, bottom: 0, right: 0 }
This option determines how accurately the Component
needs to be within the viewport for it to be considered as entered. Add bottom margin to preemptively trigger didEnterViewport.
For IntersectionObserver, this property interpolates to rootMargin.For rAF, this property will use bottom
tolerance and measure against the height of the container to determine when to trigger didEnterViewport.
Also, if your sentinel (component that uses this mixin) is a zero-height element, ensure that the sentinel actually is able to enter the viewport.
You can set application wide defaults for ember-in-viewport
in your app (they are still manually overridable inside of a Component). To set new defaults, just add a config object to config/environment.js
, like so:
module.exports = function(environment) {
var ENV = {
// ...
viewportConfig: {
viewportEnabled : false,
viewportUseRAF : true,
viewportSpy : false,
viewportScrollSensitivity : 1,
viewportRefreshRate : 100,
viewportListeners : [],
intersectionThreshold : 0,
scrollableArea : null,
viewportTolerance: {
top : 0,
left : 0,
bottom : 0,
right : 0
}
}
};
};
// Note if you want to disable right and left in-viewport triggers, set these values to `Infinity`.
Using with Modifiers is easy.
You can either use our built in modifier {{in-viewport}}
or a more verbose, but potentially more flexible generic modifier. Let's start with the former.
{{in-viewport}}
modifier on target elementoptions
are optional - see Advanced usage (options)This modifier is useful for a variety of scenarios where you need to watch a sentinel. With template only components, functionality like this is even more important! If you have logic that currently uses the did-insert
modifier to start watching an element, try this one out!
If you need more than our built in modifier...
did-insert
hook inside a componentNote - This is in lieu of a did-enter-viewport
modifier, which we plan on adding in the future. Compared to the solution below, did-enter-viewport
won't need a container (this
) passed to it. But for now, to start using modifiers, this is the easy path.
import Component from '@glimmer/component';
import { action, set } from '@ember/object';
import InViewportMixin from 'ember-in-viewport';
export default class Infinity extends Component.extend(InViewportMixin) {
@action
setupInViewport(element) {
set(this, 'viewportSpy', true);
set(this, 'viewportTolerance', {
bottom: 300
});
this.watchElement(element);
}
didEnterViewport() {
// this will only work with one element being watched in the container. This is still a TODO to enable
this.infinityLoad();
}
}
Special note: The service based approach allows you to absolve yourself from using a mixin in native classes!
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
export default class MyClass extends Component {
@service inViewport
@action
setupInViewport() {
const loader = document.getElementById('loader');
const viewportTolerance = { bottom: 200 };
const { onEnter, _onExit } = this.inViewport.watchElement(loader, { viewportTolerance });
onEnter(this.didEnterViewport.bind(this));
}
didEnterViewport() {
// do some other stuff
this.infinityLoad();
}
willDestroy() {
// need to manage cache yourself if you don't use the mixin
const loader = document.getElementById('loader');
this.inViewport.stopWatching(loader);
super.willDestroy(...arguments);
}
}
And with Classes + Modifiers!
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
export default class MyClass extends Component {
@service inViewport
@action
setupInViewport(element) {
const viewportTolerance = { bottom: 200 };
const { onEnter, onExit } = this.inViewport.watchElement(element, { viewportTolerance });
onEnter(this.didEnterViewport.bind(instance));
}
didEnterViewport() {
// do some other stuff
}
willDestroy() {
// need to manage cache yourself if you don't use the mixin
const loader = document.getElementById('loader');
this.inViewport.stopWatching(loader);
super.willDestroy(...arguments);
}
}
Options as the second argument to inViewport.watchElement
include intersectionThreshold
, scrollableArea
, viewportSpy
&& viewportTolerance
.
didEnterViewport
, didExitViewport
These hooks fire once whenever the Component
enters or exits the viewport. You can handle them the same way you would handle any other native Ember hook:
import Component from '@ember/component';
import InViewportMixin from 'ember-in-viewport';
export default Component.extend(InViewportMixin, {
didEnterViewport() {
console.log('entered');
},
didExitViewport() {
console.log('exited');
}
});
didScroll(up,down,left,right)
The didScroll
hook fires when an element enters the viewport. For example, if you scrolled down in order to move the element in the viewport, the didScroll
hook would fire and also receive the direction as a string. You can then handle it like another hook as in the above example. This is only available with the Mixin and likely not something you will need.
import Component from '@ember/component';
import InViewportMixin from 'ember-in-viewport';
export default Component.extend(InViewportMixin, {
didScroll(direction) {
console.log(direction); // 'up' || 'down' || 'left' || 'right'
}
});
viewportEntered
To apply an .active
class to your Component
when it enters the viewport, you can simply bind the active
class to the mixed in property viewportEntered
, like so:
import Component from '@ember/component';
import InViewportMixin from 'ember-in-viewport';
export default Component.extend(InViewportMixin, {
classNameBindings: [ 'viewportEntered:active' ]
});
viewportExited
This hook fires whenever the Component
leaves the viewport.
Chrome | 51 [1] |
Firefox (Gecko) | 55 [2] |
MS Edge | 15 |
Internet Explorer | Not supported |
Opera [1] | 38 |
Safari | Safari Technology Preview |
Chrome for Android | 59 |
Android Browser | 56 |
Opera Mobile | 37 |
isIntersecting
until later versions.dom.IntersectionObserver.enabled
.ember serve
ember test
ember test --serve
ember build
For more information on using ember-cli, visit http://www.ember-cli.com/.
DockYard, Inc © 2015
Licensed under the MIT license
We're grateful to these wonderful contributors who've contributed to ember-in-viewport
:
https://developer.mozilla.org/en-US/docs/Mobile/Viewport_meta_tag http://www.paulund.co.uk/understanding-the-viewport-meta-tag http://blog.javierusobiaga.com/stop-using-the-viewport-tag-until-you-know
手机的DPI高于PC显示器。在移动端浏览器中以及某些桌面浏览器中,window对象有一个devicePixelRatio属性。这个属性可以反映出手机和一般PC在DPI上的差距。比如vivo x27手机其devicePixelRatio值为3,就是x27的DPI是一般PC的3倍。网页中定义的像素尺寸在PC上看起来大小正好的话,放到该手机上物理大小只有PC上的1/3(像素是一样多的),看起来比较吃力。
Ember检查器是一个浏览器插件,用于调试Ember应用程序。 灰烬检查员包括以下主题 - S.No. 灰烬检查员方式和描述 1 安装Inspector 您可以安装Ember检查器来调试您的应用程序。 2 Object Inspector Ember检查器允许与Ember对象进行交互。 3 The View Tree 视图树提供应用程序的当前状态。 4 检查路由,数据选项卡和库信息 您可以看到检查
英文原文: http://emberjs.com/guides/getting-ember/index/ Ember构建 Ember的发布管理团队针对Ember和Ember Data维护了不同的发布方法。 频道 最新的Ember和Ember Data的 Release,Beta 和 Canary 构建可以在这里找到。每一个频道都提供了一个开发版、最小化版和生产版。更多关于不同频道的信息可以查看博客
MySQL 中的 IN 运算符用来判断表达式的值是否位于给出的列表中;如果是,返回值为 1,否则返回值为 0。 NOT IN 的作用和 IN 恰好相反,NOT IN 用来判断表达式的值是否不存在于给出的列表中;如果不是,返回值为 1,否则返回值为 0。 IN 和 NOT IN 的语法格式如下: expr IN ( value1, value2, value3 ... valueN ) expr N
问题内容: 假设我有一个表,该表的列的取值范围是1到10。我需要选择除9和10以外的所有值的列。使用此查询时是否会有区别(在性能方面): 和这个? 问题答案: 当涉及到性能,你应该总是分析代码(即运行查询数千次,并使用某种测量每个回路的性能。样品)。 但是在这里,我强烈建议您使用第一个查询来更好地维护将来。逻辑是您需要除9和10之外的所有记录。如果将值11添加到表中并使用第二个查询,则应用程序的逻
ember-emojione ember-emojione is your emoji solution for Ember, based on the EmojiOne project. EmojiOne version 2 is used, which is free to use for everyone (CC BY-SA 4.0), you're only required to giv
Ember 3D Ember 3D is an Ember addon for using Three.js - an easy to use, lightweight, javascript 3D library. It is designed to: Prescribe a solid file structure to Three.js code using ES6 modules. Ena