1 | <script src="dist/js/tether.js"></script>
|
2 | <script src="docs/js/markAttachment.js"></script>
|
3 | <script src="docs/js/intro.js"></script>
|
4 | <link rel="stylesheet" href="docs/css/intro.css"></link>
|
5 |
|
6 | Tether
|
7 | ======
|
8 |
|
9 | Tether is a JavaScript library for efficiently making an absolutely positioned
|
10 | element stay next to another element on the page. For example, you might
|
11 | want a tooltip or dialog to open, and remain, next to the relevant item
|
12 | on the page.
|
13 |
|
14 | Tether includes the ability to constrain the element within the viewport, its
|
15 | scroll parent, any other element on the page, or a fixed bounding box. When it
|
16 | exceeds those constraints it can be pinned to the edge, flip to the other
|
17 | side of its target, or hide itself.
|
18 |
|
19 | Tether optimizes its location placement to result in the minimum amount of
|
20 | 'jankyness' as the page is scrolled and resized. The page can maintain 60fps
|
21 | scrolling even with dozens or hundreds of tethers on screen (pop open the
|
22 | devtools timeline as you scroll this page).
|
23 |
|
24 | Tether is 5kb minified and gzipped, and supports IE9+, and all modern
|
25 | browsers.
|
26 |
|
27 | <h2 class="projects-header">Projects Built With Tether</h2>
|
28 | <p class="projects-paragraph">
|
29 | <a href="http://github.hubspot.com/select/docs/welcome"><span>Select</span><img src="http://github.hubspot.com/os-icons/select-icon.png" /></a>
|
30 | <a href="http://github.hubspot.com/drop/docs/welcome"><span>Drop</span><img src="http://github.hubspot.com/os-icons/drop-icon.png" /></a>
|
31 | <a href="http://github.hubspot.com/tooltip/docs/welcome"><span>Tooltip</span><img src="http://github.hubspot.com/os-icons/tooltip-icon.png" /></a>
|
32 | <a href="http://github.hubspot.com/shepherd/docs/welcome"><span>Shepherd</span><img src="http://github.hubspot.com/os-icons/shepherd-icon.png" /></a>
|
33 | </p>
|
34 |
|
35 | Usage
|
36 | -----
|
37 |
|
38 | The element to be moved is called the 'element'.
|
39 | The element in the page it's to be attached to is called the 'target'.
|
40 |
|
41 | To use Tether, you define a point on the target and a point on the element.
|
42 | Tether moves the element to keep those two points on top of each other.
|
43 |
|
44 | That point is called the attachment (we've marked it in the examples with
|
45 | a red <span class="attachment-mark"></span>). For example, if you'd like
|
46 | the element to sit on the left of the target:
|
47 |
|
48 | <pre class="pre-with-output"><code class="lang-javascript" data-example='usage'>new Tether({
|
49 | element: yellowBox,
|
50 | target: greenBox,
|
51 | attachment: 'top right',
|
52 | targetAttachment: 'top left'
|
53 | });
|
54 | </code></pre><output data-example='usage'></output>
|
55 |
|
56 | Attachment
|
57 | ----------
|
58 |
|
59 | You can move the attachment points of both the element and the target.
|
60 |
|
61 | For example, lets move the element's attachment:
|
62 |
|
63 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
64 | element: yellowBox,
|
65 | target: greenBox,
|
66 | attachment: <mark>'bottom left'</mark>,
|
67 | targetAttachment: 'top left'
|
68 | });
|
69 | </code></pre><output></output>
|
70 |
|
71 | We can also change the target's attachment point:
|
72 |
|
73 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
74 | element: yellowBox,
|
75 | target: greenBox,
|
76 | attachment: 'bottom left',
|
77 | targetAttachment: <mark>'bottom right'</mark>
|
78 | });
|
79 | </code></pre><output></output>
|
80 |
|
81 | There are two more attachment points we haven't seen yet, center and middle:
|
82 |
|
83 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
84 | element: yellowBox,
|
85 | target: greenBox,
|
86 | attachment: <mark>'middle center'</mark>,
|
87 | targetAttachment: <mark>'middle center'</mark>
|
88 | });
|
89 | </code></pre><output></output>
|
90 |
|
91 | All told, Tether provides six built in attachment positions:
|
92 |
|
93 | - left
|
94 | - center
|
95 | - right
|
96 | - top
|
97 | - middle
|
98 | - bottom
|
99 |
|
100 | The syntax of the attachment properties is: `"vertical-attachment horizontal-attachment"`.
|
101 |
|
102 | You must always supply an `attachment`. If you don't supply a `target-attachment`, it is
|
103 | assumed to be the mirror image of `attachment`.
|
104 |
|
105 | ### Offset
|
106 |
|
107 | The six attachment points we provide are not always enough to place the element
|
108 | exactly where you want it. To correct this, we provide two more properties,
|
109 | `offset` and `targetOffset`.
|
110 |
|
111 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
112 | element: yellowBox,
|
113 | target: greenBox,
|
114 | attachment: 'top right',
|
115 | targetAttachment: 'top left',
|
116 | <mark>offset: '0 10px'</mark>
|
117 | });
|
118 | </code></pre><output></output>
|
119 |
|
120 | As you can see, we've moved the attachment point of the element 10px to the right.
|
121 | We can also move the attachment point of the target:
|
122 |
|
123 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
124 | element: yellowBox,
|
125 | target: greenBox,
|
126 | attachment: 'top right',
|
127 | targetAttachment: 'top left',
|
128 | offset: '0 10px',
|
129 | <mark>targetOffset: '20px 0'</mark>
|
130 | });
|
131 | </code></pre><output></output>
|
132 |
|
133 | The offset properties also accept percentages. Percentages in `offset` refer to
|
134 | the height and width of the element, `targetOffset` the height and width of
|
135 | the target.
|
136 |
|
137 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
138 | element: yellowBox,
|
139 | target: greenBox,
|
140 | attachment: 'top right',
|
141 | targetAttachment: 'top left',
|
142 | targetOffset: <mark>'0 75%'</mark>
|
143 | });
|
144 | </code></pre><output></output>
|
145 |
|
146 | The syntax of the offset properties is `"vertical-offset horizontal-offset"`
|
147 |
|
148 | Tether offers a couple of special attachments, using the `targetModifier`
|
149 | option:
|
150 |
|
151 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
152 | element: yellowBox,
|
153 | target: scrollBox,
|
154 | attachment: 'middle right',
|
155 | targetAttachment: 'middle left',
|
156 | targetModifier: 'scroll-handle'
|
157 | });
|
158 | </code></pre><output></output>
|
159 |
|
160 | Set the target to `document.body` to have the element follow the page's scroll bar.
|
161 |
|
162 | The `targetModifier` `visible` can be used to attach an element to the visible part
|
163 | of an element:
|
164 |
|
165 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
166 | element: yellowBox,
|
167 | target: document.body,
|
168 | attachment: 'middle center',
|
169 | targetAttachment: 'middle center',
|
170 | <mark>targetModifier: 'visible'</mark>
|
171 | });
|
172 | </code></pre><output deactivated></output>
|
173 |
|
174 | <pre class="pre-with-output"><code class="lang-javascript" data-example="scroll-visible">new Tether({
|
175 | element: yellowBox,
|
176 | <mark>target: scrollBox</mark>,
|
177 | attachment: 'middle center',
|
178 | targetAttachment: 'middle center',
|
179 | targetModifier: 'visible'
|
180 | });
|
181 | </code></pre><output class="no-green scroll-page" data-example="scroll-visible"></output>
|
182 |
|
183 | Constraints
|
184 | -----------
|
185 |
|
186 | If you have tried any of the previous examples, you'll notice that it's pretty
|
187 | easy to scroll the regions in such a way that the element is hanging out on
|
188 | its own, with no target in sight.
|
189 |
|
190 | Constraints allow you to control what happens when the tethered element would
|
191 | have to fall outside of a defined region to maintain the attachment.
|
192 |
|
193 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
194 | element: yellowBox,
|
195 | target: greenBox,
|
196 | attachment: 'middle left',
|
197 | targetAttachment: 'middle left',
|
198 | <mark>constraints</mark>: [
|
199 | {
|
200 | to: 'scrollParent',
|
201 | pin: true
|
202 | }
|
203 | ]
|
204 | });
|
205 | </code></pre><output></output>
|
206 |
|
207 | We've created a constraint which will keep the element within its scroll
|
208 | parent by 'pinning' it to the edges if it tries to escape. For the sake
|
209 | of the example, we're also highlighting the pinned edge in red.
|
210 |
|
211 | Specify an array of sides if you'd only like to pin those edges:
|
212 |
|
213 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
214 | element: yellowBox,
|
215 | target: greenBox,
|
216 | attachment: 'middle left',
|
217 | targetAttachment: 'middle left',
|
218 | constraints: [
|
219 | {
|
220 | to: 'scrollParent',
|
221 | pin: <mark>['top']</mark>
|
222 | }
|
223 | ]
|
224 | });
|
225 | </code></pre><output></output>
|
226 |
|
227 | You might want to allow the element to change its attachment, if doing so
|
228 | would keep more of it within its assigned region:
|
229 |
|
230 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
231 | element: yellowBox,
|
232 | target: greenBox,
|
233 | attachment: 'top left',
|
234 | targetAttachment: 'bottom left',
|
235 | constraints: [
|
236 | {
|
237 | to: 'scrollParent',
|
238 | <mark>attachment: 'together'</mark>
|
239 | }
|
240 | ]
|
241 | });
|
242 | </code></pre><output></output>
|
243 |
|
244 | If you scroll the example a bit, you'll see it flip the attachment when necessary.
|
245 | You can combine `pin` and `attachment` as well:
|
246 |
|
247 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
248 | element: yellowBox,
|
249 | target: greenBox,
|
250 | attachment: 'top left',
|
251 | targetAttachment: 'bottom left',
|
252 | constraints: [
|
253 | {
|
254 | to: 'scrollParent',
|
255 | attachment: 'together',
|
256 | <mark>pin: true</mark>
|
257 | }
|
258 | ]
|
259 | });
|
260 | </code></pre><output></output>
|
261 |
|
262 | Attachment will accept any of these values:
|
263 |
|
264 | - `element`: Only change the element's attachment
|
265 | - `target`: Only change the target's attachment
|
266 | - `both`: Change either's attachment (or both), as needed
|
267 | - `together`: Change both the element's and target's attachment at the same time (to
|
268 | 'flip' the element to the other side of the attachment)
|
269 | - `none`: Don't allow changes to attachment (the default)
|
270 |
|
271 | Together is the option you will use most commonly:
|
272 |
|
273 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
274 | element: yellowBox,
|
275 | target: greenBox,
|
276 | attachment: 'top right',
|
277 | targetAttachment: 'bottom left',
|
278 | constraints: [
|
279 | {
|
280 | to: 'scrollParent',
|
281 | attachment: <mark>'together'</mark>
|
282 | }
|
283 | ]
|
284 | });
|
285 | </code></pre><output></output>
|
286 |
|
287 | You can also provide different settings for the vertical and horizontal attachments:
|
288 |
|
289 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
290 | element: yellowBox,
|
291 | target: greenBox,
|
292 | attachment: 'top left',
|
293 | targetAttachment: 'bottom left',
|
294 | constraints: [
|
295 | {
|
296 | to: 'scrollParent',
|
297 | attachment: <mark>'together none'</mark>
|
298 | }
|
299 | ]
|
300 | });
|
301 | </code></pre><output></output>
|
302 |
|
303 | Whenever the element is out of the constrained area, we add the `tether-out-of-bounds`
|
304 | class to it. If you add some CSS to make items with that class `display: none`, the
|
305 | tether will hide.
|
306 |
|
307 | <pre class="pre-with-output"><code class="lang-javascript" data-example="hide">new Tether({
|
308 | element: yellowBox,
|
309 | target: greenBox,
|
310 | attachment: 'middle center',
|
311 | targetAttachment: 'middle center',
|
312 | constraints: [
|
313 | {
|
314 | to: 'scrollParent'
|
315 | }
|
316 | ]
|
317 | });
|
318 | </code></pre><output data-example="hide"></output>
|
319 |
|
320 | You can also constrain the element to the viewport, you'll have to scroll the
|
321 | page to see this one.
|
322 |
|
323 | <pre class="pre-with-output"><code class="lang-javascript" data-example="window">new Tether({
|
324 | element: yellowBox,
|
325 | target: greenBox,
|
326 | attachment: 'top left',
|
327 | targetAttachment: 'bottom left',
|
328 | constraints: [
|
329 | {
|
330 | to: <mark>'window'</mark>,
|
331 | attachment: 'together'
|
332 | }
|
333 | ]
|
334 | });
|
335 | </code></pre><output data-example="window" class="scroll-page"></output>
|
336 |
|
337 | You can, of course, use pin with the window as well to
|
338 | make it always visible no matter where the user scrolls:
|
339 |
|
340 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
341 | element: yellowBox,
|
342 | target: greenBox,
|
343 | attachment: 'top left',
|
344 | targetAttachment: 'bottom left',
|
345 | constraints: [
|
346 | {
|
347 | to: 'window',
|
348 | attachment: 'together',
|
349 | <mark>pin: true</mark>
|
350 | }
|
351 | ]
|
352 | });
|
353 | </code></pre><output deactivated class="scroll-page visible-enabled"></output>
|
354 |
|
355 | `to` can be any of:
|
356 |
|
357 | - `'scrollParent'`
|
358 | - `'window'`
|
359 | - any DOM element
|
360 | - an array of bound points relative to the body `[X1, Y1, X2, Y2]`
|
361 |
|
362 | You can also provide multiple constraints, keeping in mind that they are
|
363 | processed in the order supplied (the last one always has the final word).
|
364 |
|
365 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
366 | element: yellowBox,
|
367 | target: greenBox,
|
368 | attachment: 'top left',
|
369 | targetAttachment: 'bottom left',
|
370 | constraints: [
|
371 | {
|
372 | to: <mark>'scrollParent'</mark>,
|
373 | pin: true
|
374 | },
|
375 | {
|
376 | to: <mark>'window'</mark>,
|
377 | attachment: 'together'
|
378 | }
|
379 | ]
|
380 | });
|
381 | </code></pre><output></output>
|
382 |
|
383 | Optimization
|
384 | ------------
|
385 |
|
386 | ### Element Moving
|
387 |
|
388 | The goal of Tether's optimizer is to not have to change the positioning
|
389 | CSS as the page is scrolled or resized. To accomplish this it looks at the
|
390 | last few positions, finds commonalities, and uses them to decide whether to
|
391 | position the element absolutely or with fixed positioning.
|
392 |
|
393 | If the element is fully contained within its scroll parent, its DOM node
|
394 | can also be moved inside the scroll parent, to avoid repaints as the
|
395 | container is scrolled.
|
396 |
|
397 | <pre class="pre-with-output"><code class="lang-javascript" data-example="optimizer">new Tether({
|
398 | element: yellowBox,
|
399 | target: greenBox,
|
400 | attachment: 'top left',
|
401 | targetAttachment: 'bottom left'
|
402 | });
|
403 | </code></pre><output data-example="optimizer"></output>
|
404 |
|
405 | We are moving where the DOM node is, so if you have CSS which styles elements
|
406 | within the offset parent, you may see some rendering changes. Also note
|
407 | that this optimization works best if the scroll parent is the offset parent.
|
408 | In other words, **the scroll parent should be made position relative, fixed or
|
409 | absolute to enable this optimization.**
|
410 |
|
411 | If you do see stylistic changes occur when the element is moved,
|
412 | you might want to disable this optimization. You can do that by
|
413 | setting `optimizations.moveElement` to false.
|
414 |
|
415 | <pre class="pre-with-output"><code class="lang-javascript" data-example="optimizer2">new Tether({
|
416 | element: yellowBox,
|
417 | target: greenBox,
|
418 | attachment: 'top left',
|
419 | targetAttachment: 'bottom left',
|
420 | optimizations: {
|
421 | <mark>moveElement: false</mark>
|
422 | }
|
423 | });
|
424 | </code></pre><output data-example="optimizer2"></output>
|
425 |
|
426 | ### GPU
|
427 |
|
428 | By default tether positions elements using CSS transforms. These transforms allow the
|
429 | tethered element to be moved as its own layer to not force a repaint of the underlying
|
430 | page.
|
431 |
|
432 | This method of positioning can cause some issues however, including color shifts and artifacts.
|
433 |
|
434 | If you experience these issues, you can disable this optimization by setting `optimizations.gpu`
|
435 | to false:
|
436 |
|
437 | <pre class="pre-with-output"><code class="lang-javascript" data-example>new Tether({
|
438 | element: yellowBox,
|
439 | target: greenBox,
|
440 | attachment: 'top left',
|
441 | optimizations: {
|
442 | <mark>gpu: false</mark>
|
443 | }
|
444 | });
|
445 | </code></pre><output></output>
|
446 |
|
447 | Methods
|
448 | -------
|
449 |
|
450 | The `Tether` constructor we've been using in these examples returns us a
|
451 | `Tether` object.
|
452 |
|
453 | The `Tether` object has these methods:
|
454 |
|
455 | - `setOptions({ options })` - Update any of the options (such as attachment)
|
456 | - `disable()` - Disable the tethering
|
457 | - `enable()` - Enable the tethering
|
458 | - `destroy()` - Disable and remove all references
|
459 | - `position()` - Manually trigger a repositioning
|
460 |
|
461 | Options
|
462 | -------
|
463 |
|
464 | The full list of options which can be passed to the `Tether` constructor and
|
465 | `setOptions`:
|
466 |
|
467 | - `element`: The DOM element, jQuery element, or a selector string of an element which will be moved
|
468 | - `target`: The DOM element, jQuery element, or a selector string of an element which the `element` will be attached to
|
469 | - `attachment`: A string of the form `'vert-attachment horiz-attachment'`
|
470 | - `vert-attachment` can be any of `'top'`, `'middle'`, `'bottom'`
|
471 | - `horiz-attachment` can be any of `'left'`, `'center'`, `'right'`
|
472 | - `targetAttachment`: A string similar to `attachment`.
|
473 | The one difference is that, if it's not provided, targetAttachment will assume the mirror
|
474 | image of `attachment`.
|
475 | - `offset`: A string of the form `'vert-offset horiz-offset'`
|
476 | - `vert-offset` and `horiz-offset` can be of the form `"20px"` or `"55%"`
|
477 | - `targetOffset`: A string similar to `offset`, but refering to the offset of the target
|
478 | - `targetModifier`: Can be set to `'visible'` or `'scroll-handle'`
|
479 | - `enabled`: Should the tether be enabled initially? Defaults to `true`.
|
480 | - `classes`: A hash of classes which should be changed or disabled
|
481 | - `classPrefix`: The prefix placed at the beginning of the default classes, defaults to `'tether'`
|
482 | - `optimizations`: A hash of optimizations, used to disable them
|
483 | - `constraints`: An array of constraint definition objects. Each definition is of the form:
|
484 | - `to`: A DOM element, bounding box, the string `'window'`, or the string `'scrollParent'`
|
485 | - `pin`: `true` or an array of strings representing the sides of the constraint
|
486 | - `attachment`: A string of the form `"vert-modifier horiz-modifier"`, or a single value
|
487 | representing both
|
488 | - Each modifier should be one of `"none"`, `"together"`, `"element"`, `"target"`, or `"both"`.
|
489 | - `outOfBoundsClass`: An alternative to `"tether-out-of-bounds"`, useful if the class
|
490 | needs to be differentiated from that of another constraint.
|
491 | - `pinnedClass`: An alternative to `"tether-pinned"`, similar to `outOfBoundsClass`.
|
492 |
|
493 | Classes
|
494 | -------
|
495 |
|
496 | Tether adds a variety of classes to the element and target to allow you to style
|
497 | them based on their tethering.
|
498 |
|
499 | You can change the prefix of the classes with the `classPrefix` option. It is `'tether'` by
|
500 | default, but you could, for example, change it to be `'bill'` if you were building the bill
|
501 | library and all the classes would be `'bill-*'`.
|
502 |
|
503 | ```javascript
|
504 | new Tether({
|
505 | classPrefix: 'bill'
|
506 | });
|
507 | ```
|
508 |
|
509 | The sass/css is similarily configurable, see
|
510 | [tooltip](https://github.com/HubSpot/tooltip/blob/master/sass/tooltip-theme-arrows.sass#L14) for
|
511 | an example of how to make your own prefixed css file.
|
512 |
|
513 | All classes can be changed or disabled with the `classes` option. For example, to change the
|
514 | `tether-element` class to be `my-box`:
|
515 |
|
516 | ```javascript
|
517 | new Tether({
|
518 | classes: {
|
519 | element: 'my-box'
|
520 | }
|
521 | });
|
522 | ```
|
523 |
|
524 | You can also disable classes you're not going to use:
|
525 |
|
526 | ```javascript
|
527 | new Tether({
|
528 | classes: {
|
529 | out-of-bounds: false
|
530 | }
|
531 | });
|
532 | ```
|
533 |
|
534 | - `tether-element` is added to the element
|
535 | - `tether-target` is added to the target
|
536 | - `tether-enabled` is added to both elements when tether is not disabled
|
537 | - `tether-element-attached-[left,right,top,bottom,middle,center]` is added to both
|
538 | elements based on the elements attachment, if the element becomes detached (for
|
539 | example, if it's pinned), that class is removed. The class reflects how the
|
540 | element is actually attached, so if a constraint changes the attachment, that
|
541 | change will be reflected in the class.
|
542 | - `tether-target-attached-[left,right,top,bottom,middle,center]` is added to both
|
543 | elements based on the target's attachment. All of the characteristics are the
|
544 | same as for element-attached.
|
545 |
|
546 | ### Constraint-related Classes
|
547 |
|
548 | - `tether-out-of-bounds`, `tether-out-of-bounds-[side]` are added to both the element and the target
|
549 | when the element is placed outside of its constraint.
|
550 | - `tether-pinned`, `tether-pinned-[side]` are added to both the element and target when a constraint
|
551 | has pinned the element to the [side] of the container.
|
552 |
|
553 | Browser Support
|
554 | ---------------
|
555 |
|
556 | Tether supports IE9+, and all modern browsers.
|
557 |
|
558 | Google doesn't support IE8, Microsoft is dropping support in a few months, and not supporting it saves
|
559 | us a whole lot of trouble. If you are interested in adding support, get in touch, we're happy to accept
|
560 | a PR.
|
561 |
|
562 | Contributing
|
563 | ------------
|
564 |
|
565 | Please contribute! Tether is developed in Coffeescript, but if that's problematic for you, feel free
|
566 | to submit pull requests which just change the JavaScript files, we can adapt them as needed.
|
567 |
|
568 | To build Tether, you need:
|
569 |
|
570 | - Node.js
|
571 |
|
572 | #### Instructions
|
573 |
|
574 | - Install the build tool
|
575 |
|
576 | ```bash
|
577 | npm install -g gulp
|
578 | ```
|
579 |
|
580 | - Install the project
|
581 |
|
582 | ```bash
|
583 | # In the project directory
|
584 | npm install
|
585 | ```
|
586 |
|
587 | - Build / Watch
|
588 |
|
589 | ```bash
|
590 | gulp
|
591 | ```
|