Skip to content

[DO NOT MERGE] [MM] Intersection Observer #111

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/error.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ Example:
});
```

## Error 3
Description: Sizes must be of type `Array` unless breakpoints have been specified


## Error 2
Description: The Plugin or Network has not been included in your bundle.
Please manually include the script tag associated with this plugin or network.
Expand All @@ -42,6 +38,10 @@ Example:
</script>
```

## Error 3
Description: Sizes must be of type `Array` unless breakpoints have been specified


## Error 4
Description: An ad must be passed into the GenericPlugin class. If your Plugin inherits from GenericPlugin
and overrides the constructor make sure you are calling "super" and that you are passing in an
Expand Down
4 changes: 4 additions & 0 deletions docs/lazy-load-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ In order to avoid the penalties imposed by loading slow creatives at load time,
The AdJS Auto Render plugin delays the loading of the creative until the creative is in the viewport. This ensures that
the creatives only load when the visitor is ready to see them.

## External Dependencies
This Plugin utilizes IntersectionObserver which is only fully supported in modern browsers.
If you require support for older browsers, please be sure to include a polyfill in your application.

## Installation
Depending on your method of implementation, AdJS packages may be installed via different methods.
Please follow the directions for your relevant method.
Expand Down
4 changes: 4 additions & 0 deletions docs/refresh-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ Viewability is a binary metric. Your creative will either generate a "viewable i

The AdJS refresh plugin helps you maximize your impressions per page by refreshing/fetching a new creative after your Ad has been considered as "viewed". By default the AutoRefresh Plugin will only refresh an Ad after 30 seconds of view time.

## External Dependencies
This Plugin utilizes IntersectionObserver which is only fully supported in modern browsers.
If you require support for older browsers, please be sure to include a polyfill in your application.

## Installation
Depending on your method of implementation, AdJS packages may be installed via different methods.
Please follow the directions for your relevant method.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "adjs",
"version": "2.0.0-beta.25",
"version": "2.0.0-beta.26",
"description": "Ad Library to simplify and optimize integration with ad networks such as DFP",
"main": "./core.js",
"types": "./types.d.ts",
Expand Down
8 changes: 4 additions & 4 deletions src/plugins/AutoRefresh.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { LOG_LEVELS } from '../types';
import dispatchEvent from '../utils/dispatchEvent';
import ScrollMonitor from '../utils/scrollMonitor';
import ITXObserver from '../utils/intersectionObserver';
import GenericPlugin from './GenericPlugin';

const ONE_SECOND = 1000;
Expand All @@ -19,18 +19,18 @@ class AutoRefresh extends GenericPlugin {
}

public beforeClear() {
ScrollMonitor.unsubscribe(this.ad.el.id);
ITXObserver.unsubscribe(this.ad.el.id);
dispatchEvent(this.ad.id, LOG_LEVELS.INFO, 'AutoRefresh Plugin', 'Ad viewability monitor has been removed.');
}

public beforeDestroy() {
ScrollMonitor.unsubscribe(this.ad.el.id);
ITXObserver.unsubscribe(this.ad.el.id);
dispatchEvent(this.ad.id, LOG_LEVELS.INFO, 'AutoRefresh Plugin', 'Ad viewability monitor has been removed.');
}

private startMonitoringViewability(): void {
const { container, configuration: { offset = 0 }, el } = this.ad;
ScrollMonitor.subscribe(
ITXObserver.subscribe(
el.id,
container,
offset,
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/AutoRender.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { LOG_LEVELS } from '../types';
import dispatchEvent from '../utils/dispatchEvent';
import ScrollMonitor from '../utils/scrollMonitor';
import ITXObserver from '../utils/intersectionObserver';
import GenericPlugin from './GenericPlugin';

class AutoRender extends GenericPlugin {
Expand All @@ -16,7 +16,7 @@ class AutoRender extends GenericPlugin {

const finalOffset = renderOffset || offset || 0;

ScrollMonitor.subscribe(
ITXObserver.subscribe(
id,
container,
finalOffset,
Expand Down
35 changes: 33 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,45 @@ export interface IAdConfiguration {
targeting?: IAdTargeting;
}

export interface IScrollMonitorRegisteredAd {
export interface IIntersectionObserverRegisteredAd {
element: HTMLElement;
offset: number;
inView: boolean;
fullyInView: boolean;
enableByScroll?: boolean;
hasViewBeenScrolled: boolean;
onEnterViewport: any[];
onFullyEnterViewport: any[];
onExitViewport: any[];
}

interface IBounds {
readonly height: number;
readonly width: number;
readonly top: number;
readonly left: number;
readonly right: number;
readonly bottom: number;
}

export interface IIntersectionObserverEntry {
readonly time: number;
readonly rootBounds: IBounds;
readonly boundingClientRect: IBounds;
readonly intersectionRect: IBounds;
readonly intersectionRatio: number;
readonly target: Element;
}

export type IntersectionObserverCallback = (entries: IntersectionObserverEntry[]) => void;

export declare class IIntersectionObserver {
public readonly root: Element | null;
public readonly rootMargin: string;
public readonly thresholds?: any;
constructor(callback: IntersectionObserverCallback, options?: IntersectionObserverInit);

public observe(target: Element): void;
public unobserve(target: Element): void;
public disconnect(): void;
public takeRecords(): IntersectionObserverEntry[];
}
119 changes: 119 additions & 0 deletions src/utils/intersectionObserver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { IIntersectionObserver, IIntersectionObserverEntry, IIntersectionObserverRegisteredAd } from '../types';

class ITXObserver {
public static observer: IIntersectionObserver;
public static registeredAds: { [key: string]: IIntersectionObserverRegisteredAd } = {};
public static adCount: number = 0;
public static hasViewBeenScrolled: boolean = false;

public static monitorViewport() {
if (ITXObserver.observer) {
return;
}

window.addEventListener('scroll', ITXObserver.onFirstScroll, false);

ITXObserver.observer = new IntersectionObserver((entries: any) => {
ITXObserver.handleIntersect(entries);
}, { threshold: [0, 0.25, 1] });
}

public static handleIntersect = (entries: IIntersectionObserverEntry[]) => {
entries.forEach((event: IIntersectionObserverEntry) => {
const ad = ITXObserver.registeredAds[event.target.id];

if (ad.enableByScroll && !ITXObserver.hasViewBeenScrolled) {
return;
}

const ratio = event.intersectionRatio;
const fullyInView = ratio === 1;
const inView = ratio > 0;

if (fullyInView && !ad.fullyInView) {
ad.onFullyEnterViewport.forEach((fn) => fn());
}

if (inView && !ad.inView) {
ad.onEnterViewport.forEach((fn) => fn());
}

if (!inView && ad.inView) {
ad.onExitViewport.forEach((fn) => fn());
}

ad.inView = inView;
ad.fullyInView = fullyInView;
});
}

public static subscribe = (
id: string,
element: HTMLElement,
offset: number = 0,
onEnterViewport?: () => any,
onFullyEnterViewport?: () => any,
onExitViewport?: () => any,
enableByScroll?: boolean,
) => {
ITXObserver.monitorViewport();

const existingAd = ITXObserver.getAdIfExists(id);

if (existingAd) {
if (onEnterViewport) {
existingAd.onEnterViewport.push(onEnterViewport);
}

if (onFullyEnterViewport) {
existingAd.onFullyEnterViewport.push(onFullyEnterViewport);
}

if (onExitViewport) {
existingAd.onExitViewport.push(onExitViewport);
}

return;
}

const ad: IIntersectionObserverRegisteredAd = {
element,
offset,
inView: false,
fullyInView: false,
enableByScroll,
onEnterViewport: onEnterViewport ? [onEnterViewport] : [],
onFullyEnterViewport: onFullyEnterViewport ? [onFullyEnterViewport] : [],
onExitViewport: onExitViewport ? [onExitViewport] : [],
};

ad.element.id = id;
ITXObserver.registeredAds[id] = ad;
ITXObserver.observer.observe(ad.element);
++ITXObserver.adCount;
}

public static unsubscribe = (id: string) => {
const ad = ITXObserver.registeredAds[id];

if (!ad) {
return;
}

ITXObserver.observer.unobserve(ad.element);

delete ITXObserver.registeredAds[id];
--ITXObserver.adCount;
}

private static getAdIfExists = (id: string) => {
return ITXObserver.registeredAds[id];
}

private static onFirstScroll = () => {
ITXObserver.hasViewBeenScrolled = true;
window.removeEventListener('scroll', ITXObserver.onFirstScroll, false);
}
}

export default ITXObserver;
123 changes: 0 additions & 123 deletions src/utils/scrollMonitor.ts

This file was deleted.

Loading