Coder Social home page Coder Social logo

bikecoders / ngx-sticky Goto Github PK

View Code? Open in Web Editor NEW
10.0 3.0 3.0 4.62 MB

Angular directive that adds sticky position to an HTML element and also applies and remove a custom class when the element is sticky positioned.

Home Page: https://bikecoders.github.io/ngx-sticky/

JavaScript 10.05% TypeScript 63.15% HTML 24.12% CSS 2.68%
npm angular anguar-lib angular-library ngx tdd travis-ci continuous-integration unit-testing sonarqube

ngx-sticky's Introduction

ngx-sticky-directive

npm version Build Status

coverage reliability rating security rating scale rating

Quality Gate Sonar Cloud

TL;DR

Angular directive that adds sticky position to an HTML element and also applies and remove a custom class when the element is sticky positioned.

<div #scContainer>
  <div
    ngxSticky
    classWhenSticky="my-sticky-class"
    [triggerOn]="'text-content'"
    [scrollContainer]="scContainer"
  >
    Sticky Title
  </div>
  <div id="text-content">
    ...
  </div>
</div>

Quick Demo

How to use it

To accomplish the desired behavior you need to take in account the following:

  • You need to have a scroll container
  • Inside the scroll container you need:
    • The sticky element
    • An element that when the sticky element bypass it, the directive will trigger the custom class. We call this triggerOn element
  • The triggerOn element must be relative positioned (has this css property position: relative)
    • We decided that you control this, because adding position: relative to an element can change the visual aspect to something not desired and you will asking why and blaming yourself for something that you are not conscious of, so following the Single Responsibility Principle this directive will not take care of that

dummy.component.scss

.scroll-container {
  border: #cccccc 1px solid;
  height: 75vh;
  overflow-y: auto;
  padding: 8px;
}

.title {
  background-color: #1c5089;
  color: #ffffff;
  font-size: 32px;
  font-weight: bold;
  margin: 0;
  padding: 16px;
}

.trigger-on {
  position: relative;
}

.my-sticky-class {
  background-color: #1c8950 !important;
  box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12),
    0 3px 5px -1px rgba(0, 0, 0, 0.4);
}

dummy.component.html

<div #scContainer>
  <div
    ngxSticky
    class="title"
    [triggerOn]="'trigger-on'"
    classWhenSticky="my-sticky-class"
    [scrollContainer]="scContainer"
  >
    Sticky Title
  </div>
  <div id="trigger-on" class="trigger-on">
    ...Insert a lot of content here
  </div>
</div>

Note

If you're using angular version >= 9 and providing the triggerOn value as a string, you need to do it this way [triggerOn]="'text-content'" because Ivy changed something about how we pass parameters. There is an issue open about it

According to Ivy Compatibility guide

Unbound inputs for directives (e.g. name in ) are now set upon creation of the view, before change detection runs (previously, all inputs were set during change detection).

Demo

Getting started

  1. Install ngx-sticky-directive:

    # using npm
    npm install ngx-sticky-directive --save
    
    # using yarn
    yarn add ngx-sticky-directive
  2. Import the installed library:

    import { StickyDirectiveModule } from 'ngx-sticky-directive';
    
    @NgModule({
      ...
      imports: [
        ...
        StickyDirectiveModule
      ]
    })
    export class AppModule { }
  3. Use it in your component

    @Component({
      selector: 'dummy-component',
      styles: [
        `
          .body-container {
            background-color: yellow;
            height: 2000px;
            overflow: scroll;
            padding: 10px;
          }
    
          .super-height {
            background-color: black;
            height: 5000px;
            position: relative;
            width: 100%;
          }
    
          #sticky-component {
            background-color: green;
            height: 50px;
            width: 100%;
            top: -10px;
            z-index: 10;
          }
    
          .when-sticky {
            background-color: magenta !important;
          }
        `,
      ],
      template: `
        <div class="body-container" #scCont>
          <div
            id="sticky-component"
            ngxSticky
            classWhenSticky="when-sticky"
            [triggerOn]="'trigger-on'"
            [scrollContainer]="scCont"
          ></div>
          <div id="trigger-on" class="super-height"></div>
        </div>
      `,
    })
    class DummyComponent {}

Properties

Name Description
`@Input() scrollContainer: string ElementRef
`@Input() triggerOn: string ElementRef
@Input() debugMode: boolean Display or hide the sentinel element.
@Input() classWhenSticky: string CSS class to be added to the target element when becomes sticky.
@Input() zIndex: number = 10 CSS zIndex value to set to the target element. By default is 10.
@Input() top: number = 0 CSS top value to set to the sticky element. By default is 0.

Why

Adding a custom class when an element becomes sticky is the objective of this directive. This is achieved by using Intersection Observer and avoid using scroll events to keep a good performance.

How it works

We don't want to use scroll events to detect when an element becomes sticky on the screen for performance reasons. That why we decided to use Intersection Observer.

The Intersection Observer is preferably applied to an element that has a scroll and it detects when a child element enters or exits the screen. So we add an invisible sentinel element to that scroll container and when it exists the screen we know when the sticky element start to be sticky, when the sentinel enters again the sticky element is not longer sticky. In our demo you can toggle the visibility of the sentinel and check how the intersection occurs.

The intention of this directive is to implement the article An event for CSS position:sticky in an Angular way.

The Intersection Observer API is highly supported across the different browsers, however if you have a target audience that is not currently being supported you can use the Intersection Observer Polyfill

References

Development tips

If you want to contribute with features, we recommend to read the section that we wrote about Development Tips that is in the Wiki

ngx-sticky's People

Contributors

dianjuar avatar osnoser1 avatar yossely avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

ngx-sticky's Issues

Create a directive mock

Useful to implement in test and inject the mock instead of the real one. Could be something like

import { Directive, Input } from '@angular/core';

@Directive({
    selector: '[appSticky]'
})
export class StickyMockDirective {
    @Input() sentinelOn: HTMLElement;
    @Input() classWhenSticky: string;
    @Input() debugMode: boolean;
}

Doesn't work inside `mat-sidenav-content`

It doesn't work inside mat-sidenav-content when setting up like this

app.component

<mat-sidenav-container>
  <mat-sidenav #sidenav>
      ......
  </mat-sidenav>

  <mat-sidenav-content>
    <div>
      <app-header></app-header>
      <router-outlet></router-outlet>
    </div>
    <app-footer></app-footer>
  </mat-sidenav-content>

</mat-sidenav-container>

page.component:

<div class="section" fxFlexFill>
  <div #section class="section-row" fxLayout="row" fxLayout.gt-sm="row" fxLayout="column">
    <div class="content" fxLayout="column" fxFlex.gt-sm="70" fxFlex="100">
         ... some content ...
    </div>

    <div #scCont fxFlex.gt-sm="30" fxFlex="100" class="sidebar">
      <div ngxSticky [scrollContainer]="scCont" triggerOn="trigger-on" classWhenSticky="when-sticky">
            ... some content ...
      </div>
      <div id="trigger-on"></div>

    </div>

  </div>

</div>

On SSR gives `ReferenceError`s for window and document

The first thing is first; it works on SSR.

But gives console errors on page loads.

At first load of the app; it gives ReferenceError: window is not defined. Full output:

ReferenceError: window is not defined
    at Object.Z8CG (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:159033:3)
    at __webpack_require__ (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:20:30)
    at new StickyDirective (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:150795:9)
    at createClass (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:79786:20)
    at createDirectiveInstance (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:79606:22)
    at createViewNodes (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:90886:38)
    at callViewAction (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:91336:13)
    at execComponentViewsAction (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:91241:13)
    at createViewNodes (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:90915:5)
    at createRootView (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:90758:5)

When surfing between pages after the app is loaded, it gives ERROR ReferenceError: document is not defined. Full output:

ERROR ReferenceError: document is not defined
    at StickyDirective.generateSentinelElement (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:150948:28)
    at StickyDirective.putSentinel (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:150966:31)
    at StickyDirective.ngAfterViewInit (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:150859:14)
    at callProviderLifecycles (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:80127:18)
    at callElementProvidersLifecycles (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:80092:13)
    at callLifecycleHooksChildrenFirst (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:80074:29)
    at checkAndUpdateView (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:90957:5)
    at callViewAction (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:91313:21)
    at execComponentViewsAction (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:91241:13)
    at checkAndUpdateView (/Volumes/WD_RAID/Documents/Projects/Web/medyaeli website/dist/server/main.js:90954:5)

Add bottom sentinel

If the element arrives to the end of the scroll container we probably want to remove the class that we put or something else.
With the bottom sentinel and intersection observer we achieve this.

Update licence

We are using a polyfil made for W3C and they have a licence.

The license grants me copy and distribution but I have to tell that I use that work...

Check the license and made the arrangements

Cannot make it work with custom sidebar

I have content and sidebar, created using FlexLayout. The content width is 70, and the sidebar width is 30. Since the content is longer than the sidebar, I want to make it sticky when I scroll down but couldn't make it work. Below, I shared a simple Stackblitz project. Any help pls? Thank you.

stackblitz

Populate SonarQube Version

Actually in sonarqube the project's version is fix on 1.0.0.

Find the way to auto-magically update the version with the current one

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.