Magnet.js is a JavaScript library that groups HTML elements and makes them attractable with each other
4 magnet groups that can attract the others in their own groups or all the other group members.
Extend the Basic demo with new features:
a
/w
/d
/s
keys)px
unit of arrow_ (_unit < distance_
_ would cause the box stuck with the others when attracted_git clone https://github.com/lf2com/magnet.js.git
cd magnet.js
npm install .
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 Magnet from '@lf2com/magnet.js';
// Or
const Magnet = require('@lf2com/magnet.js');
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-magnet.min.js
npm run jquery-build
Build both
./magnet.min.js
and./jquery-magnet.min.js
npm run all-build
Append
-debug
on anybuild
command
npm run build-debug
# for jQuery
npm run jquery-build-debug
# for both
npm run all-build-debug
Download from this repository or use your own built: [magnet.min.js](https://lf2com.github.io/magnet.js/magnet.min.js)
<!-- include script -->
<script src="PATH/TO/magnet.min.js"></script>
<script>
console.log(window.Magnet); // here it is
</script>
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>
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
let options = {
distance: 15,
stayInParent: true,
};
let $magnet = $.magnet(options);
Add HTML elements into the group
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
Add elements to an existing group
Add element to a new group
let $magnet = $('.magnet').magnet(options);
Remove HTML elements from the group
Keep the positon changed by the magnet
magnet.remove(document.querySelector('.magnet')); // return this
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
Remove all the HTML elements from the group
Keep the position changed by the magnet
magnet.clear();
Remove the position changed by the magnet
magnet.clearFull();
jQuery
Distance for elements to attract others in the same group
Default:
_0_
(px)
Get/set distance
magnet.distance(15); // set: unit px, return this
magnet.distance(); // get: 15
Alias
magnet.setDistance(15); // set to 15
magnet.getDistance(); // get 15
jQuery
Attractable between group members
_Default: _
_true_
NOTICE: Setting to
false
has the same effect as pressingctrl
key
Get/set attractable
magnet.attractable(true); // set to attract members, return this
magnet.attractable(); // get: true
Alias
magnet.setAttractable(true); // set to true
magnet.getAttractable(); // get true
jQuery
Ctrl
KeyAllow 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
Get/set allow ctrl key
magnet.allowCtrlKey(true); // set to allow ctrl key, return this
magnet.allowCtrlKey(); // get: true
Alias
magnet.setAllowCtrlKey(true); // set to true
magnet.getAllowCtrlKey(); // get true
jQuery
Allow to drag element by mouse/touch
_Default: _
_true_
Get/set allow drag
magnet.allowDrag(true); // set to allow drag, return this
manget.allowDrag(); // get: true
Alias
magnet.setAllowDrag(true); // set to true
magnet.getAllowDrag(); // get true
jQuery
Use relative unit %
or absolute unit px
_Default: _
_false_
Get/set use relative unit
magnet.useRelativeUnit(true); // set to use relative unit, return this
magnet.useRelativeUnit(); // get: true
Alias
magnet.setUseRelativeUnit(true); // set to true
magnet.getUseRelativeUnit(); // get true
jQuery
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 |
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
magnet.enabledAlignOuter(true); // set to true
magnet.enabledAlignParentCenter(false); // set to false
magnet.enabledAlignOuter(); // get: true
magnet.enabledAlignParentCenter(); // get: false
magnet.setEnabledAlignOuter(true); // set to true
magnet.getEnabledAlignOuter(); // get true
jQuery
CAUTION:
_parentNode_
of the current element.__parentNode_
whose _style.position_
is not __static_
_top_
/_left_
_ offset of magnet members is based on the parent element_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
magnet.stayInParentEdge(true); // set to true
magnet.stayInParentEdge(); // get: true
magnet.stayInParentElem(true); // set to true
magnet.stayInParentElem(); // get true
Another alias
magnet.setStayInParent(true); // set to true
magnet.getStayInParent(); // get true
jQuery
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 |
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 |
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
Remove event listeners
magnet.off('magnetenter magnetleave magnetchange'); // remove event listeners
// the same as above
magnet
.off('magnetenter')
.off('magnetleave')
.off('magnetchange');
jQuery
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
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
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
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
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 the relationships between source
and all the other group members
_Default
_sourceRect_
is the rectangle of __sourceDOM_
Default
_alignments_
is the outer/inner/center settings of magnet
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
Change the position of target member for the input position with checking the attracting relationships between source
and all the other group members
_Default
_sourceRect_
is the rectangle of __sourceDOM_
_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
Directly change the position of member that is faster than .handle(...)
_Default
_sourceRect_
is the rectangle of __sourceDOM_
_Default
_useRelativeUnit_
is the value of _[_.getUseRelativeUnit()_](https://github.com/lf2com/magnet.js#use-relative-unit)
let { top, right, bottom, left } = elem.getBoundingClientRect();
magnet.setMemberRectangle(elem, rect);
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
Property | Type | Description |
---|---|---|
origin | Object | Origin rectangle object |
target | Object | Target rectangle object |
Property | Type | Description |
---|---|---|
current | Object | Current attract infos of x/y axises |
last | Object | Last attract infos of x/y axises |
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(beforeAttractFunc); // set function
$magnet.beforeAttract(); // get beforeAttractFunc
$magnet.beforeAttract(null); // unset function (input non-function value)
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
See what changed after attracting
jQuery
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)
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 2 elements/rectangles
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(rectA, rectB);
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.
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 |
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) |
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 |
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 |
Property | Type | Description |
---|---|---|
rect | Object | Rectangle object |
element (optional) | DOM | HTML element. undefined if the source is a pure rectangle like 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 |
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 |
min | String | Alignment property with minimum distance |
max | String | Alignment property with maximum distance |
Author: lf2com
Demo: https://lf2com.github.io/magnet.js/demo/demo_types.html
Source Code: https://github.com/lf2com/magnet.js
#javascript #html #css