README
Magnet.js
Magnet.js is a JavaScript library that groups HTML elements and makes them attractable with each other
Demo
Basic demo
- Configure magnet attract distance
- Switch to stay in parent element
- Align to outer/inner edge of the others
- Align to the x/y center of the others
- Align to the x/y center of parent element
Group demo
4 magnet groups that can attract the others in their own groups or all the other group members.
Arrow key demo
Extend the Basic demo with new features:
- Support arrow keys to move focused box (also support
a
/w
/d
/s
keys) - Configure
px
unit of arrow_ ( unit < distance
would cause the box stuck with the others when attracted
Install
Git
git clone https://github.com/lf2com/magnet.js.git
cd magnet.js
npm install .
NodeJS
CAUTION: Magnet.js is not tested on NodeJS environment. It uses
document
andeventListener
related functions.
npm install @lf2com/magnet.js
# Or
npm install https://github.com/lf2com/magnet.js
Import
import Magnet from '@lf2com/magnet.js'; // Or const Magnet = require('@lf2com/magnet.js');
Build
The required files are ./index.js
and ./libs/*.js
. All dependencies in ./package.json
are only used for building a packaged/minified JS file as ./magnet.min.js
. Since the code registered as window.Magnet
. You can build a browser-used magnet.min.js
with the following commands:
npm run build
Build jQuery Plugin
Build
./jquery-magnet.min.js
npm run jquery-build
Build All
Build both
./magnet.min.js
and./jquery-magnet.min.js
npm run all-build
Debug Build
Append
-debug
on anybuild
commandnpm run build-debug # for jQuery npm run jquery-build-debug # for both npm run all-build-debug
Browser
Download from this repository or use your own built: magnet.min.js
<!-- include script -->
<script src="PATH/TO/magnet.min.js"></script>
<script>
console.log(window.Magnet); // here it is
</script>
jQuery Plugin
NOTICE: Please include jQuery library before incluing
jquery-magnet.min.js
<script src="PATH/TO/jQuery.js"></script> <script src="PATH/TO/jquery-magnet.min.js"></script> <script> (function($) { console.log($.magnet); // here it is })(jQuery); </script>
Usage of Magnet
Create Magnet Group
Create a magnet group. All the elements added into the group would be applied the attract behaviors.
let magnet = new Magnet();
jQuery
Create a new group
options?)
$.magnet(let options = { distance: 15, stayInParent: true, }; let $magnet = $.magnet(options);
Add Elements
Add HTML elements into the group
.add(...DOMs)
magnet.add(document.querySelectorAll('.magnet')); // return this
Or add HTML element when creating a group
let magnet = new Magnet(document.querySelectorAll('.magnet'));
Flexable ways to add elements
magnet.add( document.querySelectorAll('.magnet'), document.querySelectorAll('.other-magnet'), document.getElementById('major-magnet') ); // the same as above magnet .add(document.querySelectorAll('.magnet')) .add(document.querySelectorAll('.other-magnet')) .add(document.getElementById('major-magnet'));
jQuery
$magnet.add(...DOMs)
Add elements to an existing group
options?)
$.fn.magnet(Add element to a new group
let $magnet = $('.magnet').magnet(options);
Remove Elements
Remove HTML elements from the group
.remove(...DOMs)
Keep the positon changed by the magnet
magnet.remove(document.querySelector('.magnet')); // return this
.removeFull(...DOMs)
Remove the positions changed by the magnet
magnet.removeFull(document.querySelector('.magnet')); // return this
Flexable ways to remove elements
magnet.remove( document.querySelectorAll('.magnet'), document.querySelectorAll('.other-magnet'), document.getElementById('major-magnet') ); // the same as above magnet .remove(document.querySelectorAll('.magnet')) .remove(document.querySelectorAll('.other-magnet')) .remove(document.getElementById('major-magnet'));
jQuery
$magnet.remove(...DOMs)
$magnet.removeFull(...DOMs)
Clear All Elements
Remove all the HTML elements from the group
.clear()
Keep the position changed by the magnet
magnet.clear();
.clearFull()
Remove the position changed by the magnet
magnet.clearFull();
jQuery
$magnet.clear()
$magnet.clearFull()
Distance of Attraction
Distance for elements to attract others in the same group
Default:
0
(px)
.distance(px?)
Get/set distance
magnet.distance(15); // set: unit px, return this
magnet.distance(); // get: 15
Alias
.setDistance(px)
magnet.setDistance(15); // set to 15
.getDistance()
magnet.getDistance(); // get 15
jQuery
$magnet.distance(px?)
Attractable
Attractable between group members
Default:
true
NOTICE: Setting to
false
has the same effect as pressingctrl
key
.attractable(enabled?)
Get/set attractable
magnet.attractable(true); // set to attract members, return this
magnet.attractable(); // get: true
Alias
.setAttractable(enabled)
magnet.setAttractable(true); // set to true
.getAttractable()
magnet.getAttractable(); // get true
jQuery
$magnet.attractable(enabled?)
Ctrl
Key
Allow Allow to press ctrl
key to be unattractable temporarily
Default:
true
NOTICE: Pressing
ctrl
key makes group members unattractable, any magnet related event will not be triggered
.allowCtrlKey(enabled?)
Get/set allow ctrl key
magnet.allowCtrlKey(true); // set to allow ctrl key, return this
magnet.allowCtrlKey(); // get: true
Alias
.setAllowCtrlKey(enabled)
magnet.setAllowCtrlKey(true); // set to true
.getAllowCtrlKey()
magnet.getAllowCtrlKey(); // get true
jQuery
$magnet.allowCtrlKey(enabled?)
Allow Drag Elements
Allow to drag element by mouse/touch
Default:
true
.allowDrag(enabled?)
Get/set allow drag
magnet.allowDrag(true); // set to allow drag, return this
manget.allowDrag(); // get: true
Alias
.setAllowDrag(enabled)
magnet.setAllowDrag(true); // set to true
.getAllowDrag()
magnet.getAllowDrag(); // get true
jQuery
$magnet.allowDrag(enabled?)
Use Relative Unit
Use relative unit %
or absolute unit px
Default:
false
.useRelativeUnit(enabled?)
Get/set use relative unit
magnet.useRelativeUnit(true); // set to use relative unit, return this
magnet.useRelativeUnit(); // get: true
Alias
.setUseRelativeUnit(enabled)
magnet.setUseRelativeUnit(true); // set to true
magnet.getUseRelativeUnit(); // get true
jQuery
$magnet.useRelativeUnit(enabled?)
Alignments
Magnet supports the following alignments:
| Type | Description | Default |
| :-: | :- | :-: |
| outer | align edges to other edges from outside | true
|
| inner | align edges to other edges from inside | true
|
| center | align middle x/y to other's middle x/y | true
|
| parent center | align middle x/y to parent's middle x/y | false
|
Prop}(enabled?)
.align{Get/set enabled of alignment
magnet.alignOuter(true); // set: align to element outside edges, return this
magnet.alignInner(false); // set: align to element inside edges, return this
magnet.alignCenter(true); // set: align to element middle line, return this
magnet.alignParentCenter(false); // set: alien to parent element middle line, return this
magnet.alignOuter(); // get: true
Alias
Prop}(enabled?)
.enabledAlign{magnet.enabledAlignOuter(true); // set to true magnet.enabledAlignParentCenter(false); // set to false magnet.enabledAlignOuter(); // get: true magnet.enabledAlignParentCenter(); // get: false
Prop}(enabled)
.setEnabledAlign{magnet.setEnabledAlignOuter(true); // set to true
Prop}()
.getEnabledAlign{magnet.getEnabledAlignOuter(); // get true
jQuery
$magnet.align{Prop}(enabled?)
Align to Parent Inner Edges
CAUTION:
- Parent may NOT be the 1st
parentNode
of the current element._- Parent is the first matched
parentNode
whosestyle.position
is notstatic
- All the
top
/left
offset of magnet members is based on the parent element
.stayInParent(enabled?)
Force elements of group not to be out of the edge of parent element
Default:
false
Get/set stay inside of the parent
magnet.stayInParent(true); // set: not to move outside of the parent element, return this
magnet.stayInParent(); // get: true
Alias
.stayInParentEdge(enabled?)
magnet.stayInParentEdge(true); // set to true magnet.stayInParentEdge(); // get: true
.stayInParentElem(enabled?)
magnet.stayInParentElem(true); // set to true magnet.stayInParentElem(); // get true
Another alias
.setStayInParent(enabled)
magnet.setStayInParent(true); // set to true
.getStayInParent()
magnet.getStayInParent(); // get true
jQuery
$magnet.stayInParent(enabled?)
Events of Magnet
Magnet supports the following events:
| Name | Description | Alias |
| :-: | :- | :-: |
| magnetstart | when the last result has no any attract but now it does | start
, magnetenter
, enter
|
| magnetend | when the last result has any attract but now it doesn't | end
, magnetleave
, leave
|
| magnetchange | when any change of attract, including start/end and the changes of attracted alignment properties | change
|
Arguments of Magnet Event
Each event has the following members in the detail of event object:
| Property | Type | Description |
| :-: | :-: | :- |
| source | DOM | HTML element that is dragged |
| x | Object | Attract info of x-axis, null
if no attract |
| y | Object | Attract info of y-axis, null
if no attract |
.on(eventNames, functions)
Add event listener
magnet.on('magnetenter', function(evt) {
let detail = evt.detail;
console.log('magnetenter', detail); // detail info of attract elements
console.log('source', detail.source); // current HTML element
console.log('targets', detail.x, detail.y); // current attracted of both axises
});
magnet.on('magnetleave', function(evt) {
let detail = evt.detail;
console.log('magnetleave', detail);
console.log('source', detail.source);
console.log('targets', detail.x, detail.y); // the last attracted of both axises
});
magnet.on('magnetchange', function(evt) {
let detail = evt.detail;
console.log('magnetchange', detail);
console.log('source', detail.source);
console.log('targets', detail.x, detail.y); // the newest attracted of both axises
});
// the same as above
magnet.on('magnetstart', function(evt) {
// do something
}).on('magnetchange', function(evt) {
// do something
}).on('magnetend', function(evt) {
// do something
});
jQuery
$magnet.on(eventNames, functions)
.off(eventNames)
Remove event listeners
magnet.off('magnetenter magnetleave magnetchange'); // remove event listeners
// the same as above
magnet
.off('magnetenter')
.off('magnetleave')
.off('magnetchange');
jQuery
$magnet.off(eventNames)
Events of magnet members
Magnet members supports the following events:
| Name | Target | description | | :-: | :-: | :- | | attract | forcused | Attract to other members | | unattract | focused | Unattract from other members | | attracted | others | Attracted by the focused member | | unattracted | others | Unattracted by the focused member | | attractstart | focused | Start of dragging | | attractend | focused | End of dragging | | attractmove | focused | Moving of dragging |
attract
/unattract
Arguments of Events of attract
and unattract
have the following members in the detail of event object:
| Property | Type | Description |
| :-: | :-: | :- |
| x | Object | Attract info of x-axis, null
if no attract |
| y | Object | Attract info of y-axis, null
if no attract |
let elem = document.querySelector('.block');
magnet.add(elem);
function onAttract(evt) {
let detail = evt.detail;
console.log('attract', detail); // detail info of attract elements
console.log('targets', detail.x, detail.y); // current attracted of both axises
}
function onUnattract(evt) {
let detail = evt.detail;
console.log('unattract', detail);
console.log('targets', detail.x, detail.y); // the last attracted of both axises
}
// add event listener
elem.addEventListener('attract', onAttract);
elem.addEventListener('unattract', onUnattract);
// remove event listener
elem.removeEventListener('attract', onAttract);
elem.removeEventListener('unattract', onUnattract);
jQuery
// the same as above $(elem) .on('attract', onAttract) .on('unattract', onUnattract); $(elem) .off('attract unattract');
attracted
/unattracted
Arguments of Events of attracted
and unattracted
have the target member in the detail of event object
function onAttracted(evt) {
let dom = evt.detail;
console.log('attracted', dom); // be attracted by dom
}
function onUnattracted(evt) {
let dom = evt.detail;
console.log('unattracted', dom); // be unattracted by dom
}
// add event listener
elem.addEventListener('attracted', onAttracted);
elem.addEventListener('unattracted', onUnattracted);
// remove event listener
elem.removeEventListener('attracted', onAttracted);
elem.removeEventListener('unattracted', onUnattracted);
jQuery
// the same as above $(elem) .on('attracted', onAttracted) .on('unattracted', onUnattracted); $(elem).off('attracted unattracted');
attractstart
/attractend
Arguments of function onAttractStart(evt) {
let rect = evt.detail;
console.log('attract start', rect); // rectangle of dom
}
function onAttractEnd(evt) {
let rect = evt.detail;
console.log('attract end', rect); // rectangle of dom
}
// add event listener
elem.addEventListener('attractstart', onAttractStart);
elem.addEventListener('attractend', onAttractEnd;
// remove event listener
elem.removeEventListener('attractstart', onAttractStart);
elem.removeEventListener('attractend', onAttractEnd
jQuery
// the same as above $(elem) .on('attractstart', onAttractStart) .on('attractend', onAttractEnd); $(elem).off('attractstart attractend');
attractmove
Arguments of NOTICE: Call
preventDefault()
to ignore attraction if need
function onAttractMove(evt) {
let { rects, attracts } = evt.detail;
let { origin, target } = rects;
let { current, last } = attracts;
// do something
// ...
evt.preventDefault(); // call this to ignore attraction if need
}
elem.addEventListener('attractmove', onAttractMove); // add event listener
elem.removeEventListener('attractmove', onAttractMove); // remove event listener
jQuery
// the same as above $(elem).on('attractmove', onAttractMove); $(elem).off('attractmove');
Check Attracting Result
Check the relationships between source
and all the other group members
.check(sourceDOM[, sourceRect[, alignments]])
Default
sourceRect
is the rectangle ofsourceDOM
Default
alignments
is the outer/inner/center settings of magnet
Parameter of Check Result
| Property | Type | Description |
| :-: | :-: | :- |
| source | Object | Element object |
| parent | Object | Element object |
| targets | Array | Array of measurement result object |
| results | Object | Object with alignment properties and the values are array of measurement results |
| rankings | Object | Object as results
but each property is sorted from near to far |
| mins | Object | Object with alignment properties and the values are the minimum value of distance |
| maxs | Object | Object with alignment properties and the values are the maximum value of distance |
magnet.add(elem);
magnet.check(elem, ['topToTop', 'bottomToBottom']); // get the result of 'topToTop' and 'bottomToBottom' between the other members
// the same as above
magnet.check(elem, elem.getBoundingClientRect(), ['topToTop', 'bottomToBottom']);
jQuery
$magnet.check(sourceDOM[, sourceRect[, alignments]])
Handle Rectangle Position of Element
Change the position of target member for the input position with checking the attracting relationships between source
and all the other group members
.handle(sourceDOM[, sourceRect[, attractable]])
Default
sourceRect
is the rectangle ofsourceDOM
Default
attractable
is the value of attractable
let { top, right, bottom, left } = elem.getBoundingClientRect();
let offset = {
x: 15,
y: 10
};
let rect = {
top: (top-offset.y),
right: (right-offset.x),
bottom: (bottom-offset.y),
left: (left-offset.x),
};
magnet.add(elem);
magnet.handle(elem, rect, true); // move the member to the new rectangle position with the attracting relationship, return this
jQuery
$magnet.handle(sourceDOM[, sourceRect[, attractable]])
Set Rectangle Position of Member
Directly change the position of member that is faster than .handle(...)
.setMemberRectangle(sourceDOM[, sourceRect[, useRelativeUnit]])
Default
sourceRect
is the rectangle ofsourceDOM
Default
useRelativeUnit
is the value of.getUseRelativeUnit()
let { top, right, bottom, left } = elem.getBoundingClientRect();
magnet.setMemberRectangle(elem, rect);
Before/After/Do Applying Rectangle Position
The group passes the info to target function before/after/do applying the change to target element
NOTICE: The function will be called with rectangle infos and attract infos as long as dragging the target element
Rectangle Infos
| Property | Type | Description | | :-: | :-: | :- | | origin | Object | Origin rectangle object | | target | Object | Target rectangle object |
Attract Infos
| Property | Type | Description | | :-: | :-: | :- | | current | Object | Current attract infos of x/y axises | | last | Object | Last attract infos of x/y axises |
.beforeAttract = function(targetDom rectangleInfos, attractInfos)
Set to a function for confirming the change
| Value | Description |
| :-: | :- |
| false
| Apply the original rectangle without attraction |
| rectangle
| Rectangle object to apply on the target element |
function beforeAttractFunc(dom, { origin, target }, { current, last }) {
console.log(this); // manget
if (MAKE_SOME_CHANGES) {
// apply other rectangle info
return {
top: (target.top - 1),
right: (target.right + 1),
bottom: (target.bottom + 1),
left: (target.left - 1),
};
} else if (NO_ATTRACTION) {
return false; // ignore attraction
} else if (STILL_NO_ATTRACTION) {
return origin; // the same as no attraction
}
// if went here, it would apply default change
};
magnet.beforeAttract = beforeAttractFunc; // set function
console.log(magnet.beforeAttract); // print function
magnet.beforeAttract = null; // unset function
jQuery
$magnet.beforeAttract(function(targetDom, rectangleInfos, attractInfos)?)
$magnet.beforeAttract(beforeAttractFunc); // set function $magnet.beforeAttract(); // get beforeAttractFunc $magnet.beforeAttract(null); // unset function (input non-function value)
.doAttract = function(targetDom, rectangleInfos, attractInfos)
Set the displacement handly which means the user has to set the style of DOM to apply the position change if need
magnet.doAttract = function(dom, { origin, target }, { current, last }) {
const { top, right, bottom, left } = origin;
const { x, y } = current;
const px = (p) => `${p}px`;
if (x && y && x.element === y.element) {
// attract current targets
const elem = x.element;
const { width, height } = x.rect;
const move = (type) => {
switch (type) {
case 'topToTop': return elem.style.top = px(top);
case 'rightToRight': return elem.style.left = px(right-width);
case 'bottomToBottom': return elem.style.top = px(bottom-height);
case 'leftToLeft': return elem.style.left = px(left);
case 'topToBottom': return elem.style.top = px(top-height);
case 'bottomToTop': return elem.style.top = px(bottom);
case 'rightToLeft': return elem.style.left = px(right);
case 'LeftToRight': return elem.style.left = px(left-width);
case 'xCenter': return elem.style.left = px((right+left-width)/2);
case 'yCenter': return elem.style.top = px((top+bottom-height)/2);
}
}
move(x.type);
move(y.type);
}
// keep original position
dom.style.top = px(top);
dom.style.left = px(left);
});
jQuery
$magnet.doAttract(function(targetDom, rectangleInfos, attractInfos)?)
.afterAttract = function(targetDom, rectangleInfos, attractInfos)
See what changed after attracting
jQuery
$magnet.afterAttract(function(targetDom, rectangleInfos, attractInfos)?)
Usage of Rectangle
Check Rectangle
Magnet.isRect(rect)
Check if rect
is a rectangle like object with the following object members and rules:
Property | Rule |
---|---|
top | <= bottom |
right | >= left |
bottom | >= top |
left | <= right |
width | = right - left |
height | = bottom - top |
x (optional) | = left |
y (optional) | = top |
NOTICE: Default use
0.0000000001
for bias of calculation
let rect = { top: 1, right: 2, bottom: 3, left: 4 };
Magnet.isRect(rect); // false: right < left
rect.right = 5;
Magnet.isRect(rect); // true
rect.x = 3;
Magnet.isRect(rect); // false: x != left
rect.x = rect.left;
rect.width = 2;
Magnet.isRect(rect); // false: width != (right - left)
Standardize Rectangle
Magnet.stdRect(rect)
Return a rectangle object if rect
is a HTML element or a valid rectangle like object:
| Property | Rule |
| :-: | :- |
| top | Inherit from rect
|
| right | Inherit from rect
|
| bottom | Inherit from rect
|
| left | Inherit from rect
|
| width | Inherit from rect
or set to right - left
|
| height | Inherit from rect
or set to bottom - top
|
| x | Inherit from rect
or set to left
|
| y | Inherit from rect
or set to top
|
Magnet.stdRect(rect); // get a rectangle object
Measure Distance between Rectangles
Magnet.measure(source, target[, options])
Measure distance between 2 elements/rectangles
Options
Options of measurement:
| Property | Type | Description |
| :-: | :-: | :- |
| alignments | Array | Array of alignment properties. Default is ALL alignment properties |
| absDistance | Boolean | false
to allow negative value of distance. Default is true
|
let rectA = { top: 0, right: 3, bottom: 1, left: 2 };
let rectB = { top: 10, right: 13, bottom: 11, left: 12 };
Magnet.measure(rectA, rectB); // MeasureResult object
Alias
Magnet.diffRect(source, target[, options])
Magnet.diffRect(rectA, rectB);
Result of Measurement
DEPRECATED Methods
Magnet.nearby(...)
To reduce the usless calculations of measurement, it's recommended to call
Magnet.measure
/Magnet.diffRect
independently and handle the results handly to get what you really want.
References
Magnet Default Values
| Property | Type | Description | Default |
| :-: | :-: | :- | :-: |
| distance | Number | Distance to attract | 0
|
| attractable | Boolean | Ability to attract | true
|
| allowCtrlKey | Boolean | Ability to use ctrl
key to unattract | true
|
| stayInParent | Boolean | Stay in parent element | false
|
| alignOuter | Boolean | Align outer edges to that of the others | true
|
| alignInner | Boolean | Align inner edges to that of the others | true
|
| alignCenter | Boolean | Align x/y center to that of the others | true
|
| alignParentCenter | Boolean | Align x/y center to that of parent element | false
|
Alignment Properties
| Value | Description |
| :-: | :- |
| topToTop | Source top
to target top
(inner) |
| rightToRight | Source right
to target right
(inner) |
| bottomToBottom | Source bottom
to target bottom
(inner) |
| leftToLeft | Source left
to target left
(inner) |
| topToBottom | Source top
to target bottom
(outer) |
| bottomToTop | Source bottom
to target top
(outer) |
| rightToLeft | Source right
to target left
(outer) |
| leftToright | Source left
to target right
(outer) |
| xCenter | Source x
middle to target x
middle (center) |
| yCenter | Source y
middle to target y
middle (center) |
Attract Info
| Property | Type | Description |
| :-: | :-: | :- |
| type | String | Alignment property name |
| rect | Object | Rectangle object of element |
| element | DOM | HTML element |
| position | Number | Absolute offset px
based on window's top/left |
| offset | Number | Offset px
based on parent element |
Rectangle Object
| Property | Type | Description |
| :-: | :-: | :- |
| top | Number | The same as y
|
| right | Number | |
| bottom | Number | |
| left | Number | The same as x
|
| width | Number | The same as right - left
|
| height | Number | The same as bottom - top
|
| x | Number | The same as left
|
| y | Number | The same as top
|
Element Object
| Property | Type | Description |
| :-: | :-: | :- |
| rect | Object | Rectangle object |
| element (optional) | DOM | HTML element. undefined
if the source is a pure rectangle like object |
Measurement Value Object
NOTICE: All the properties inherit from alignment properties
Value | Type |
---|---|
topToTop (optional) | Number |
rightToRight (optional) | Number |
bottomToBottom (optional) | Number |
leftToLeft (optional) | Number |
topToBottom (optional) | Number |
bottomToTop (optional) | Number |
rightToLeft (optional) | Number |
leftToright (optional) | Number |
xCenter (optional) | Number |
yCenter (optional) | Number |
Measurement Result Object
| Property | Type | Description | | :-: | :-: | :- | | source | Object | Element object | | target | Object | Element object | | results | Object | Measurement value object. The properties follow the input alignment properties of measurement | ranking | Array | Array of alignment properties sorted from near to far | | min | String | Alignment property with minimum distance | | max | String | Alignment property with maximum distance |
NOTICE: The following properties are DEPRECATED
Property Type Replacement topToTop Number results.topToTop
topToBottom Number results.topToBottom
rightToRight Number results.rightToRight
rightToLeft Number results.rightToLeft
bottomToTop Number results.bottomToTop
bottomToBottom Number results.bottomToBottom
xCenter Number results.xCenter
yCenter Number results.yCenter
License
MIT Copyright @ Wan Wan