├── .editorconfig
├── .eslintrc
├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── nwb.config.js
├── package-lock.json
├── package.json
└── src
└── index.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*.{css,html,js,scss}]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "parserOptions": {
4 | "ecmaVersion": 6,
5 | "sourceType": "module"
6 | },
7 | "env": {
8 | "browser": true
9 | },
10 | "globals": {
11 | "module": true,
12 | },
13 | "extends": "eslint:recommended",
14 | "rules": {
15 | "arrow-parens": 2,
16 | "brace-style": [2, "stroustrup"],
17 | "indent": [2, 2],
18 | "no-else-return": 2,
19 | "semi": [2, "always"]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /coverage
2 | /demo/dist
3 | /es
4 | /lib
5 | /node_modules
6 | /umd
7 | npm-debug.log*
8 | .DS_Store
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: node_js
4 | node_js:
5 | - 6
6 |
7 | branches:
8 | only:
9 | - master
10 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Prerequisites
2 |
3 | [Node.js](http://nodejs.org/) >= v4 must be installed.
4 |
5 | ## Installation
6 |
7 | - Running `npm install` in the module's root directory will install everything you need for development.
8 |
9 | ## Building
10 |
11 | - `npm run build` will build the module for publishing to npm.
12 |
13 | - `npm run clean` will delete built resources.
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Matt Stow
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 | # eqio
14 |
15 | **A simple, tiny alternative to element/container queries**
16 |
17 | eqio allows you to attain the holy grail of responsive web development/design systems: components that can adapt their styling based on *their* width, not the browser‘s.
18 |
19 | It uses [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)s under-the-hood (so is [well supported](https://caniuse.com/#feat=intersectionobserver) in “good” browsers and [easily polyfilled](https://github.com/w3c/IntersectionObserver) in others) to apply appropriately named classes to the component when necessary.
20 |
21 | ## Table of Contents
22 |
23 | - [Demo](#demo)
24 | - [Installation](#installation)
25 | - [npm](#npm)
26 | - [Direct `
48 | ```
49 |
50 | ## Usage
51 |
52 | ### The HTML
53 |
54 | 1. Add a class of `eqio` to the element.
55 | 2. Add a `data-eqio-sizes` attribute whose value is a JSON-serializable array of sizes.
56 | 3. Optionally add a `data-eqio-prefix` attribute whose value is used as the prefix for your class names.
57 |
58 | ```html
59 |
64 | …
65 |
66 | ```
67 |
68 | The above component will:
69 |
70 | * be able to be customised when *its* width is 400 or smaller (`"<"` is a synonym for `max-width`, not “less than”).
71 | * be able to be customised when *its* width is 700 or greater (`">"` is a synonym for `min-width`, not “greater than”).
72 | * apply the following classes `media-object-eqio-<400` and `media-object-eqio->700` as appropriate. If `data-eqio-prefix` had not been specified, the applied classes would be `eqio-<400` and `eqio->700`.
73 |
74 | *Note: Multiple classes can be applied at once.*
75 |
76 | ### The CSS
77 |
78 | In your CSS, write class names that match those applied to the HTML.
79 |
80 | ```scss
81 | .media-object-eqio-\<400 {
82 | /* customizations when less than or equal to 400px */
83 | }
84 |
85 | .media-object-eqio-\>700 {
86 | /* customizations when greater than or equal to 700px */
87 | }
88 | ```
89 |
90 | *Note:*
91 | * *eqio classes include the special characters `<` & `>`, so they must be escaped with a `\` in your CSS.*
92 | * *eqio elements are `position: relative` by default, but your component can override that to `absolute`/`fixed` etc as required.*
93 | * *eqio elements can't be anything but `overflow: visible`.*
94 | * *To prevent accidental creation of horizontal scrollbars, a parent element is required to `overflow-x: hidden`. It is recommended to apply this to `body`.*
95 |
96 | ### The JavaScript
97 |
98 | If using a module bundler, such as webpack, first import `Eqio`.
99 |
100 | ```js
101 | import Eqio from 'eqio';
102 | ```
103 |
104 | In your JS, tell eqio which elements are to be made responsive by passing a DOM reference to `Eqio`.
105 |
106 | ```js
107 | var mediaObject = new Eqio(document.querySelector('.media-object'));
108 | ```
109 |
110 | Should you need to stop this element from being responsive, you can call `.stop()` on your instance:
111 |
112 | ```js
113 | mediaObject.stop();
114 | ```
115 |
116 | This will remove all observers and eqio internals, except for the class names that were applied at the time.
117 |
118 | ---
119 |
120 | Copyright (c) 2018 [Matt Stow](http://mattstow.com)
121 | Licensed under the MIT license *(see [LICENSE](https://github.com/stowball/eqio/blob/master/LICENSE) for details)*
122 |
--------------------------------------------------------------------------------
/nwb.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | type: 'web-module',
3 | npm: {
4 | esModules: true,
5 | umd: {
6 | global: 'Eqio',
7 | }
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eqio",
3 | "version": "0.1.3",
4 | "description": "A simple, tiny alternative to element/container queries",
5 | "main": "lib/index.js",
6 | "module": "es/index.js",
7 | "files": [
8 | "es",
9 | "lib",
10 | "umd"
11 | ],
12 | "scripts": {
13 | "build-web-module": "nwb build-web-module",
14 | "build": "npm-run-all lint build-web-module",
15 | "clean": "nwb clean-module",
16 | "lint": "eslint src"
17 | },
18 | "dependencies": {},
19 | "devDependencies": {
20 | "babel-eslint": "^7.2.3",
21 | "eslint": "^4.1.0",
22 | "npm-run-all": "^4.0.2",
23 | "nwb": "0.18.x"
24 | },
25 | "author": "Matt Stow ",
26 | "homepage": "http://mattstow.com",
27 | "license": "MIT",
28 | "repository": "https://github.com/stowball/eqio"
29 | }
30 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | class Eqio {
2 | constructor(el) {
3 | if (!el || !('IntersectionObserver' in window)) {
4 | return;
5 | }
6 |
7 | this.init(el);
8 | }
9 |
10 | init(el) {
11 | this.el = el;
12 |
13 | this.createStyles();
14 | this.createTriggers();
15 | this.createObservers();
16 | }
17 |
18 | createStyles() {
19 | if (!document.getElementById('eqio-req-css')) {
20 | const style = document.createElement('style');
21 | const css = `
22 | .eqio {
23 | position: relative;
24 | }
25 |
26 | .eqio__trigger {
27 | height: 1px;
28 | left: 0;
29 | pointer-events: none;
30 | position: absolute;
31 | top: 0;
32 | visibility: hidden;
33 | z-index: -1;
34 | }`;
35 |
36 | style.id = 'eqio-req-css';
37 | style.type = 'text/css';
38 | style.appendChild(document.createTextNode(css));
39 | document.head.insertBefore(style, document.head.children[0]);
40 | }
41 | }
42 |
43 | createTriggers() {
44 | this.sizesArray = JSON.parse(this.el.attributes['data-eqio-sizes'].value);
45 | this.triggerEls = [];
46 | let triggerEl;
47 | const fragment = document.createDocumentFragment();
48 |
49 | this.sizesArray.forEach((trigger) => {
50 | triggerEl = document.createElement('div');
51 | triggerEl.className = 'eqio__trigger';
52 | triggerEl.setAttribute('data-eqio-size', trigger);
53 | triggerEl.style.width = `${trigger.slice(1)}px`;
54 |
55 | this.triggerEls.push(triggerEl);
56 | fragment.appendChild(triggerEl);
57 | });
58 |
59 | this.el.appendChild(fragment);
60 | }
61 |
62 | createObservers() {
63 | const prefix = this.el.attributes['data-eqio-prefix'] ? `${this.el.attributes['data-eqio-prefix'].value}-` : '';
64 | const className = `${prefix}eqio-`;
65 | const observerOptions = {
66 | root: this.el,
67 | rootMargin: '0px',
68 | threshold: 1,
69 | };
70 |
71 | const observerCallback = (entries) => {
72 | entries.forEach((entry) => {
73 | const size = entry.target.dataset.eqioSize;
74 |
75 | if (size.indexOf('>') === 0) {
76 | if (entry.intersectionRatio === 1) {
77 | this.el.classList.add(`${className}${size}`);
78 | }
79 | else {
80 | this.el.classList.remove(`${className}${size}`);
81 | }
82 | }
83 | else {
84 | if (entry.intersectionRatio === 1) {
85 | this.el.classList.remove(`${className}${size}`);
86 | }
87 | else {
88 | this.el.classList.add(`${className}${size}`);
89 | }
90 | }
91 | });
92 | };
93 |
94 | this.observer = new IntersectionObserver(observerCallback, observerOptions);
95 | this.sizesArray.forEach((size, index) => {
96 | this.observer.observe(this.triggerEls[index]);
97 | });
98 | }
99 |
100 | stop() {
101 | this.el.removeAttribute('data-eqio-sizes');
102 |
103 | this.sizesArray.forEach((size, index) => {
104 | this.observer.unobserve(this.triggerEls[index]);
105 | });
106 |
107 | this.triggerEls.forEach((trigger) => {
108 | this.el.removeChild(trigger);
109 | });
110 |
111 | delete this.el;
112 | delete this.observer;
113 | delete this.sizesArray;
114 | delete this.triggerEls;
115 | }
116 | }
117 |
118 | module.exports = Eqio;
119 |
--------------------------------------------------------------------------------