UNPKG

19.3 kBMarkdownView Raw
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
6Tether
7======
8
9Tether is a JavaScript library for efficiently making an absolutely positioned
10element stay next to another element on the page. For example, you might
11want a tooltip or dialog to open, and remain, next to the relevant item
12on the page.
13
14Tether includes the ability to constrain the element within the viewport, its
15scroll parent, any other element on the page, or a fixed bounding box. When it
16exceeds those constraints it can be pinned to the edge, flip to the other
17side of its target, or hide itself.
18
19Tether 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
21scrolling even with dozens or hundreds of tethers on screen (pop open the
22devtools timeline as you scroll this page).
23
24Tether is 5kb minified and gzipped, and supports IE9+, and all modern
25browsers.
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
35Usage
36-----
37
38The element to be moved is called the 'element'.
39The element in the page it's to be attached to is called the 'target'.
40
41To use Tether, you define a point on the target and a point on the element.
42Tether moves the element to keep those two points on top of each other.
43
44That point is called the attachment (we've marked it in the examples with
45a red <span class="attachment-mark"></span>). For example, if you'd like
46the 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
56Attachment
57----------
58
59You can move the attachment points of both the element and the target.
60
61For 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
71We 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
81There 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
91All told, Tether provides six built in attachment positions:
92
93- left
94- center
95- right
96- top
97- middle
98- bottom
99
100The syntax of the attachment properties is: `"vertical-attachment horizontal-attachment"`.
101
102You must always supply an `attachment`. If you don't supply a `target-attachment`, it is
103assumed to be the mirror image of `attachment`.
104
105### Offset
106
107The six attachment points we provide are not always enough to place the element
108exactly 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
120As you can see, we've moved the attachment point of the element 10px to the right.
121We 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
133The offset properties also accept percentages. Percentages in `offset` refer to
134the height and width of the element, `targetOffset` the height and width of
135the 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
146The syntax of the offset properties is `"vertical-offset horizontal-offset"`
147
148Tether offers a couple of special attachments, using the `targetModifier`
149option:
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
160Set the target to `document.body` to have the element follow the page's scroll bar.
161
162The `targetModifier` `visible` can be used to attach an element to the visible part
163of 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
183Constraints
184-----------
185
186If you have tried any of the previous examples, you'll notice that it's pretty
187easy to scroll the regions in such a way that the element is hanging out on
188its own, with no target in sight.
189
190Constraints allow you to control what happens when the tethered element would
191have 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
207We've created a constraint which will keep the element within its scroll
208parent by 'pinning' it to the edges if it tries to escape. For the sake
209of the example, we're also highlighting the pinned edge in red.
210
211Specify 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
227You might want to allow the element to change its attachment, if doing so
228would 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
244If you scroll the example a bit, you'll see it flip the attachment when necessary.
245You 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
262Attachment 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
271Together 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
287You 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
303Whenever the element is out of the constrained area, we add the `tether-out-of-bounds`
304class to it. If you add some CSS to make items with that class `display: none`, the
305tether 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
320You can also constrain the element to the viewport, you'll have to scroll the
321page 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
337You can, of course, use pin with the window as well to
338make 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
362You can also provide multiple constraints, keeping in mind that they are
363processed 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
383Optimization
384------------
385
386### Element Moving
387
388The goal of Tether's optimizer is to not have to change the positioning
389CSS as the page is scrolled or resized. To accomplish this it looks at the
390last few positions, finds commonalities, and uses them to decide whether to
391position the element absolutely or with fixed positioning.
392
393If the element is fully contained within its scroll parent, its DOM node
394can also be moved inside the scroll parent, to avoid repaints as the
395container 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
405We are moving where the DOM node is, so if you have CSS which styles elements
406within the offset parent, you may see some rendering changes. Also note
407that this optimization works best if the scroll parent is the offset parent.
408In other words, **the scroll parent should be made position relative, fixed or
409absolute to enable this optimization.**
410
411If you do see stylistic changes occur when the element is moved,
412you might want to disable this optimization. You can do that by
413setting `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
428By default tether positions elements using CSS transforms. These transforms allow the
429tethered element to be moved as its own layer to not force a repaint of the underlying
430page.
431
432This method of positioning can cause some issues however, including color shifts and artifacts.
433
434If you experience these issues, you can disable this optimization by setting `optimizations.gpu`
435to 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
447Methods
448-------
449
450The `Tether` constructor we've been using in these examples returns us a
451`Tether` object.
452
453The `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
461Options
462-------
463
464The 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
493Classes
494-------
495
496Tether adds a variety of classes to the element and target to allow you to style
497them based on their tethering.
498
499You can change the prefix of the classes with the `classPrefix` option. It is `'tether'` by
500default, but you could, for example, change it to be `'bill'` if you were building the bill
501library and all the classes would be `'bill-*'`.
502
503```javascript
504new Tether({
505 classPrefix: 'bill'
506});
507```
508
509The sass/css is similarily configurable, see
510[tooltip](https://github.com/HubSpot/tooltip/blob/master/sass/tooltip-theme-arrows.sass#L14) for
511an example of how to make your own prefixed css file.
512
513All 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
517new Tether({
518 classes: {
519 element: 'my-box'
520 }
521});
522```
523
524You can also disable classes you're not going to use:
525
526```javascript
527new 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
538elements based on the elements attachment, if the element becomes detached (for
539example, if it's pinned), that class is removed. The class reflects how the
540element is actually attached, so if a constraint changes the attachment, that
541change will be reflected in the class.
542- `tether-target-attached-[left,right,top,bottom,middle,center]` is added to both
543elements based on the target's attachment. All of the characteristics are the
544same 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
549when 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
551has pinned the element to the [side] of the container.
552
553Browser Support
554---------------
555
556Tether supports IE9+, and all modern browsers.
557
558Google doesn't support IE8, Microsoft is dropping support in a few months, and not supporting it saves
559us a whole lot of trouble. If you are interested in adding support, get in touch, we're happy to accept
560a PR.
561
562Contributing
563------------
564
565Please contribute! Tether is developed in Coffeescript, but if that's problematic for you, feel free
566to submit pull requests which just change the JavaScript files, we can adapt them as needed.
567
568To build Tether, you need:
569
570- Node.js
571
572#### Instructions
573
574- Install the build tool
575
576```bash
577npm install -g gulp
578```
579
580- Install the project
581
582```bash
583# In the project directory
584npm install
585```
586
587- Build / Watch
588
589```bash
590gulp
591```