├── .gitignore
├── README.md
├── demo
├── index.html
├── index.js
├── main.css
├── now.json
├── package-lock.json
└── package.json
├── index.ts
├── package-lock.json
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # tackjs
2 | Tiny utility to position an element absolutely in relation to another element. **500b gzipped.**
3 |
4 | > What's this for? Think popovers, modals, tooltips, scroll-jacking, etc.
5 |
6 | ## Install
7 | ```bash
8 | npm i tackjs --save
9 | ```
10 |
11 | ## Usage
12 | Usage is very straightfoward. Think, "pin *element* to *target* at the *top*":
13 | ```javascript
14 | import tack from 'tackjs'
15 |
16 | const element = document.querySelector('...')
17 | const target = document.querySelector('...')
18 |
19 | const pin = tack(element, target, 'top')
20 | ```
21 |
22 | To update the position – say after the window resizes – use `update`:
23 | ```javascript
24 | pin.update()
25 | ```
26 |
27 | If you need to un-pin and remove all styles:
28 | ```javascript
29 | pin.destroy()
30 | ```
31 |
32 | But don't worry! It can be re-pinned as well:
33 | ```javascript
34 | pin.update()
35 | ```
36 |
37 | **N.B.** `tackjs` also adds an `.is-tacked` class to all pinned elements.
38 |
39 | ### Alignment
40 | Tack supports the following coordinates relative to the passed `target` element:
41 | - `top`
42 | - `bottom`
43 | - `left`
44 | - `right`
45 | - `topLeft`
46 | - `topRight`
47 | - `bottomLeft`
48 | - `bottomRight`
49 |
50 | ## License
51 | MIT License © [Eric Bailey](https://estrattonbailey.com)
52 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | tackjs 📌
7 |
8 |
9 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
🍕
21 |
22 |
tackjs
23 |
A minimal toolkit to pin an element relative to another element.
24 |
25 |
👉 Open your browser console to play around.
26 |
27 |
28 |
29 | 0
30 |
31 |
32 | 1
33 |
34 |
35 | 2
36 |
37 |
38 | 3
39 |
40 |
41 |
42 | View this project on GitHub.
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/demo/index.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('DOMContentLoaded', e => {
2 | window.pizza = document.getElementById('target')
3 | window.box = [
4 | document.getElementById('box0'),
5 | document.getElementById('box1'),
6 | document.getElementById('box2'),
7 | document.getElementById('box3')
8 | ]
9 | window.tack = window.tackjs
10 |
11 | console.log(`welcome to the tackjs demo 📌
12 |
13 | attached to the window are a few objects for you to mess with:
14 | 1. pizza - a tasty piece of pizza
15 | 2. box - the array of tomato colored rectangles
16 | 3. tackjs library, available as \`window.tack\`
17 |
18 | use them like this:
19 |
20 | tack(pizza, box[1], 'top')
21 |
22 | 💡 think "tack pizza to box 1 at the top"
23 |
24 | also, tack returns some utils:
25 |
26 | const pin = tack(pizza, box[3], 'bottomLeft')
27 |
28 | pin.update() // update position, like after resize
29 | pin.destroy() // unpin
30 |
31 | bonus points: attach an event listener, like resize, and call update on each tick to maintain pinned position.
32 | `)
33 | })
34 |
--------------------------------------------------------------------------------
/demo/main.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 | html, body {
5 | padding: 0;
6 | margin: 0;
7 | font-family: monospace;
8 | }
9 | .outer {
10 | padding: 2em;
11 | }
12 | .container {
13 | max-width: 600px;
14 | margin: auto;
15 | }
16 | .flex {
17 | display: flex;
18 | flex-wrap: wrap;
19 | margin-left: -1em;
20 | margin-right: -1em;
21 | }
22 | .box {
23 | width: 50%;
24 | padding: 1em;
25 |
26 | div {
27 | display: block;
28 | position: relative;
29 | padding-top: 100%;
30 | background-color: tomato;
31 | }
32 | span {
33 | padding: 1em;
34 | font-size: 1.5em;
35 | position: absolute;
36 | top: 0;
37 | }
38 | }
39 | #target {
40 | position: absolute;
41 | left: 0;
42 | top: 0;
43 | width: 2em;
44 | height: 2em;
45 | background: rgba(0,0,0,0.8);
46 | transition: transform 200ms cubic-bezier(0,.58,.38,1);
47 | }
48 |
--------------------------------------------------------------------------------
/demo/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "alias": "tackjs.now.sh"
4 | }
5 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "tackjs-demo",
4 | "scripts": {
5 | "deploy": "now --target production"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | type Placement =
2 | | "top"
3 | | "bottom"
4 | | "left"
5 | | "right"
6 | | "topLeft"
7 | | "bottomLeft"
8 | | "topRight"
9 | | "bottomRight";
10 |
11 | export function getCoords(element: HTMLElement) {
12 | const {
13 | left: l,
14 | right: r,
15 | top: t,
16 | bottom: b
17 | } = element.getBoundingClientRect();
18 | const { pageYOffset: y } = window;
19 |
20 | return {
21 | height: b - t,
22 | width: r - l,
23 | top: {
24 | y: y + t,
25 | x: l + (r - l) / 2
26 | },
27 | bottom: {
28 | y: y + b,
29 | x: l + (r - l) / 2
30 | },
31 | left: {
32 | y: y + t + (b - t) / 2,
33 | x: l
34 | },
35 | right: {
36 | y: y + t + (b - t) / 2,
37 | x: r
38 | },
39 | topLeft: {
40 | y: y + t,
41 | x: l
42 | },
43 | bottomLeft: {
44 | y: y + b,
45 | x: l
46 | },
47 | topRight: {
48 | y: y + t,
49 | x: r
50 | },
51 | bottomRight: {
52 | y: y + b,
53 | x: r
54 | }
55 | };
56 | }
57 |
58 | export function position(
59 | target: HTMLElement,
60 | scope: HTMLElement,
61 | placement: Placement
62 | ) {
63 | const c = getCoords(scope)[placement];
64 | const e = getCoords(target);
65 | const { pageYOffset: y } = window;
66 |
67 | const vp = {
68 | top: y,
69 | bottom: y + window.innerHeight,
70 | left: 0,
71 | right: window.innerWidth
72 | };
73 |
74 | const offsets = {
75 | top: {
76 | x: e.width / 2,
77 | y: e.height
78 | },
79 | bottom: {
80 | x: e.width / 2,
81 | y: 0
82 | },
83 | left: {
84 | x: e.width,
85 | y: e.height / 2
86 | },
87 | right: {
88 | x: 0,
89 | y: e.height / 2
90 | },
91 | topLeft: {
92 | x: e.width,
93 | y: e.height
94 | },
95 | topRight: {
96 | x: 0,
97 | y: e.height
98 | },
99 | bottomLeft: {
100 | x: e.width,
101 | y: 0
102 | },
103 | bottomRight: {
104 | x: 0,
105 | y: 0
106 | }
107 | };
108 |
109 | let posx = c.x - offsets[placement].x;
110 | let posy = c.y - offsets[placement].y;
111 |
112 | if (posx < vp.left) {
113 | posx = vp.left;
114 | } else if (posx + e.width > vp.right) {
115 | posx = vp.right - e.width;
116 | }
117 |
118 | if (posy < vp.top) {
119 | posy = vp.top;
120 | } else if (posy + e.height > vp.bottom) {
121 | posy = vp.bottom - e.height;
122 | }
123 |
124 | target.style.transform = `translateX(${Math.round(
125 | posx
126 | )}px) translateY(${Math.round(posy)}px)`;
127 | }
128 |
129 | export function tack(
130 | target: HTMLElement,
131 | scope: HTMLElement,
132 | placement: Placement
133 | ) {
134 | target.classList.add("is-tacked");
135 | position(target, scope, placement);
136 |
137 | return {
138 | update() {
139 | position(target, scope, placement);
140 | },
141 | destroy() {
142 | target.style.transform = "";
143 | target.classList.remove("is-tacked");
144 | }
145 | };
146 | }
147 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tackjs",
3 | "version": "2.1.0",
4 | "description": "A simple toolkit to pin an element relative to another element.",
5 | "source": "index.ts",
6 | "module": "dist/tackjs.es.js",
7 | "main": "dist/tackjs.js",
8 | "umd:main": "dist/tackjs.umd.js",
9 | "types": "dist/index.d.ts",
10 | "files": [
11 | "dist"
12 | ],
13 | "scripts": {
14 | "build": "microbundle build",
15 | "watch": "microbundle watch",
16 | "test": "ava"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+ssh://git@github.com/estrattonbailey/tackjs.git"
21 | },
22 | "author": "estrattonbailey",
23 | "keywords": [
24 | "tack",
25 | "positioning"
26 | ],
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/estrattonbailey/tackjs/issues"
30 | },
31 | "homepage": "https://github.com/estrattonbailey/tackjs#readme",
32 | "devDependencies": {
33 | "ava": "^2.1.0",
34 | "microbundle": "^0.11.0",
35 | "ts-node": "^8.6.0"
36 | },
37 | "ava": {
38 | "compileEnhancements": false,
39 | "extensions": [
40 | "ts"
41 | ],
42 | "require": [
43 | "ts-node/register"
44 | ]
45 | }
46 | }
47 |
--------------------------------------------------------------------------------