Move and resize square boxes with Vanilla JSAnimations with vanilla JSSimple login form with vanilla HTML...
Do ranged attacks with improvised weapons get the bonus from the archery fighting style?
Changing Color of error messages
What does "^L" mean in c?
Do I need to be arrogant to get ahead?
awkward or wrong?
What is the term when voters “dishonestly” choose something that they do not want to choose?
Bash - pair each line of file
While on vacation my taxi took a longer route, possibly to scam me out of money. How can I deal with this?
Is it insecure to send a password in a `curl` command?
Why is indicated airspeed rather than ground speed used during the takeoff roll?
Regex aa* = a*a?
What can I do if I am asked to learn different programming languages very frequently?
Violin - Can double stops be played when the strings are not next to each other?
Print a physical multiplication table
Hausdorff dimension of the boundary of fibres of Lipschitz maps
Do US professors/group leaders only get a salary, but no group budget?
Worshiping one God at a time?
How to get the n-th line after a grepped one?
Assassination attempts against Kosh
Do I need to consider instance restrictions when showing a language is in P?
Deletion of copy-ctor & copy-assignment - public, private or protected?
How to generate binary array whose elements with values 1 are randomly drawn
How can my new character not be a role-playing handicap to the party?
Using Leaflet inside Bootstrap container?
Move and resize square boxes with Vanilla JS
Animations with vanilla JSSimple login form with vanilla HTML markupWeb page with draggable boxes in a gridGrabbing elements with a “Move Up” buttonGoogle Maps JavaScript API v3: Sorting Markers with Check BoxesLooping over text boxes and editing valuesAbstraction of google analytics with click events and forms submission handlingPopup classes hierarchy designAdd and remove classes on resizeResize and compress images with imagick
$begingroup$
Feature requirements.
- I can move a square box.
- I can resize a square box by stretching four edges.
- Many square boxes can exist.
I've implemented moving and resizing square boxes by module pattern. Of course, I thought making a square box as a Class
. But I think that using module pattern is better in this case.
Module Pattern
- Pros
- Can manage only focused element. Don't need to care about unfocused elements.
- Can control event handling in it.
- Can hide private variables and methods.
- Cons
- Maybe need many changes by changing features.
- Test issue.
Class
- Pros
- Can manage and maintain properties of each square box.
- Can be more clean code.
- Cons
- Should manage many instances.
- Should add additional information to the DOM for finding each instance.
So, I implemented as module pattern. It looks like not good for test and can be more clear code. What should I do more for clean code or more testable?
https://jsfiddle.net/docf1t40/2/
<!DOCTYPE html>
<html>
<head>
<title>Practice</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.plate {
position: absolute;
width: 100%;
height: 100%;
background: rgb(240, 240, 240);
}
.edge {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgb(211, 211, 211);
}
.edge.lt {
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.edge.rt {
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.edge.lb {
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.edge.rb {
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
</style>
</head>
<body>
<script type="text/javascript">
const Square = (function() {
let userAction;
let focusedElement = {
DOM: null,
width: 0,
height: 0,
screenX: 0,
screenY: 0,
translateX: 0,
translateY: 0,
};
function focusElement(dom, width, height, sx, sy, tx, ty) {
focusedElement.DOM = dom;
focusedElement.width = width;
focusedElement.height = height;
focusedElement.screenX = sx;
focusedElement.screenY = sy;
focusedElement.translateX = tx;
focusedElement.translateY = ty;
}
function blurElement() {
focusedElement = {
DOM: null,
width: 0,
height: 0,
screenX: 0,
screenY: 0,
translateX: 0,
translateY: 0,
};
}
function getMovement(sx, sy) {
return {
x: sx - focusedElement.screenX,
y: sy - focusedElement.screenY
};
}
function move(sx, sy) {
const movement = getMovement(sx, sy);
const tx = focusedElement.translateX + movement.x;
const ty = focusedElement.translateY + movement.y;
focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
}
function resize(sx, sy) {
const movement = getMovement(sx, sy);
let tx = focusedElement.translateX;
let ty = focusedElement.translateY;
let width = focusedElement.width;
let height = focusedElement.height;
switch (userAction) {
case 'RESIZE-LT':
width = focusedElement.width - movement.x;
height = focusedElement.height - movement.y;
tx = focusedElement.translateX + movement.x;
ty = focusedElement.translateY + movement.y;
break;
case 'RESIZE-RT':
width = focusedElement.width + movement.x;
height = focusedElement.height - movement.y;
ty = focusedElement.translateY + movement.y;
break;
case 'RESIZE-LB':
width = focusedElement.width - movement.x;
height = focusedElement.height + movement.y;
tx = focusedElement.translateX + movement.x;
break;
case 'RESIZE-RB':
width = focusedElement.width + movement.x;
height = focusedElement.height + movement.y;
break;
}
width = Math.max(50, width);
height = Math.max(50, height);
focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
focusedElement.DOM.style.width = `${width}px`;
focusedElement.DOM.style.height = `${height}px`;
}
function onMouseDown(e) {
if (e.target && e.target.dataset && e.target.dataset.userAction) {
let tx = 0;
let ty = 0;
const transform = e.target.parentNode.style.transform;
const matchTranslate = transform.match(/translate((-?d+.?d*)px ?, ?(-?d+.?d*)px)/);
if (matchTranslate) {
tx = parseInt(matchTranslate[1]);
ty = parseInt(matchTranslate[2]);
}
focusElement(
e.target.parentNode,
parseInt(e.target.parentNode.style.width),
parseInt(e.target.parentNode.style.height),
e.screenX,
e.screenY,
tx,
ty
);
userAction = e.target.dataset.userAction;
}
}
function onMouseUp(e) {
blurElement();
userAction = null;
}
function onMouseMove(e) {
switch (userAction) {
case 'MOVE':
move(e.screenX, e.screenY);
break;
case 'RESIZE-LT':
case 'RESIZE-RT':
case 'RESIZE-LB':
case 'RESIZE-RB':
resize(e.screenX, e.screenY);
break;
}
}
return {
create: function(x, y, width, height) {
const div = document.createElement('div');
div.setAttribute('style', `position:absolute; width:${width}px; height:${height}px; transform: translate(${x}px, ${y}px)`);
div.innerHTML = `<div data-user-action="MOVE" class="plate"></div>
<span data-user-action="RESIZE-LT" class="edge lt"></span>
<span data-user-action="RESIZE-RT" class="edge rt"></span>
<span data-user-action="RESIZE-LB" class="edge lb"></span>
<span data-user-action="RESIZE-RB" class="edge rb"></span>`;
document.body.appendChild(div);
},
onMouseDownListener: onMouseDown,
onMouseUpListener: onMouseUp,
onMouseMoveListener: onMouseMove,
}
})();
document.addEventListener('DOMContentLoaded', function() {
Square.create(300, 300, 100, 200);
Square.create(200, 100, 80, 80);
document.addEventListener('mousedown', Square.onMouseDownListener);
document.addEventListener('mouseup', Square.onMouseUpListener);
document.addEventListener('mousemove', Square.onMouseMoveListener);
});
</script>
</body>
</html>
javascript html dom revealing-module-pattern
$endgroup$
add a comment |
$begingroup$
Feature requirements.
- I can move a square box.
- I can resize a square box by stretching four edges.
- Many square boxes can exist.
I've implemented moving and resizing square boxes by module pattern. Of course, I thought making a square box as a Class
. But I think that using module pattern is better in this case.
Module Pattern
- Pros
- Can manage only focused element. Don't need to care about unfocused elements.
- Can control event handling in it.
- Can hide private variables and methods.
- Cons
- Maybe need many changes by changing features.
- Test issue.
Class
- Pros
- Can manage and maintain properties of each square box.
- Can be more clean code.
- Cons
- Should manage many instances.
- Should add additional information to the DOM for finding each instance.
So, I implemented as module pattern. It looks like not good for test and can be more clear code. What should I do more for clean code or more testable?
https://jsfiddle.net/docf1t40/2/
<!DOCTYPE html>
<html>
<head>
<title>Practice</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.plate {
position: absolute;
width: 100%;
height: 100%;
background: rgb(240, 240, 240);
}
.edge {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgb(211, 211, 211);
}
.edge.lt {
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.edge.rt {
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.edge.lb {
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.edge.rb {
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
</style>
</head>
<body>
<script type="text/javascript">
const Square = (function() {
let userAction;
let focusedElement = {
DOM: null,
width: 0,
height: 0,
screenX: 0,
screenY: 0,
translateX: 0,
translateY: 0,
};
function focusElement(dom, width, height, sx, sy, tx, ty) {
focusedElement.DOM = dom;
focusedElement.width = width;
focusedElement.height = height;
focusedElement.screenX = sx;
focusedElement.screenY = sy;
focusedElement.translateX = tx;
focusedElement.translateY = ty;
}
function blurElement() {
focusedElement = {
DOM: null,
width: 0,
height: 0,
screenX: 0,
screenY: 0,
translateX: 0,
translateY: 0,
};
}
function getMovement(sx, sy) {
return {
x: sx - focusedElement.screenX,
y: sy - focusedElement.screenY
};
}
function move(sx, sy) {
const movement = getMovement(sx, sy);
const tx = focusedElement.translateX + movement.x;
const ty = focusedElement.translateY + movement.y;
focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
}
function resize(sx, sy) {
const movement = getMovement(sx, sy);
let tx = focusedElement.translateX;
let ty = focusedElement.translateY;
let width = focusedElement.width;
let height = focusedElement.height;
switch (userAction) {
case 'RESIZE-LT':
width = focusedElement.width - movement.x;
height = focusedElement.height - movement.y;
tx = focusedElement.translateX + movement.x;
ty = focusedElement.translateY + movement.y;
break;
case 'RESIZE-RT':
width = focusedElement.width + movement.x;
height = focusedElement.height - movement.y;
ty = focusedElement.translateY + movement.y;
break;
case 'RESIZE-LB':
width = focusedElement.width - movement.x;
height = focusedElement.height + movement.y;
tx = focusedElement.translateX + movement.x;
break;
case 'RESIZE-RB':
width = focusedElement.width + movement.x;
height = focusedElement.height + movement.y;
break;
}
width = Math.max(50, width);
height = Math.max(50, height);
focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
focusedElement.DOM.style.width = `${width}px`;
focusedElement.DOM.style.height = `${height}px`;
}
function onMouseDown(e) {
if (e.target && e.target.dataset && e.target.dataset.userAction) {
let tx = 0;
let ty = 0;
const transform = e.target.parentNode.style.transform;
const matchTranslate = transform.match(/translate((-?d+.?d*)px ?, ?(-?d+.?d*)px)/);
if (matchTranslate) {
tx = parseInt(matchTranslate[1]);
ty = parseInt(matchTranslate[2]);
}
focusElement(
e.target.parentNode,
parseInt(e.target.parentNode.style.width),
parseInt(e.target.parentNode.style.height),
e.screenX,
e.screenY,
tx,
ty
);
userAction = e.target.dataset.userAction;
}
}
function onMouseUp(e) {
blurElement();
userAction = null;
}
function onMouseMove(e) {
switch (userAction) {
case 'MOVE':
move(e.screenX, e.screenY);
break;
case 'RESIZE-LT':
case 'RESIZE-RT':
case 'RESIZE-LB':
case 'RESIZE-RB':
resize(e.screenX, e.screenY);
break;
}
}
return {
create: function(x, y, width, height) {
const div = document.createElement('div');
div.setAttribute('style', `position:absolute; width:${width}px; height:${height}px; transform: translate(${x}px, ${y}px)`);
div.innerHTML = `<div data-user-action="MOVE" class="plate"></div>
<span data-user-action="RESIZE-LT" class="edge lt"></span>
<span data-user-action="RESIZE-RT" class="edge rt"></span>
<span data-user-action="RESIZE-LB" class="edge lb"></span>
<span data-user-action="RESIZE-RB" class="edge rb"></span>`;
document.body.appendChild(div);
},
onMouseDownListener: onMouseDown,
onMouseUpListener: onMouseUp,
onMouseMoveListener: onMouseMove,
}
})();
document.addEventListener('DOMContentLoaded', function() {
Square.create(300, 300, 100, 200);
Square.create(200, 100, 80, 80);
document.addEventListener('mousedown', Square.onMouseDownListener);
document.addEventListener('mouseup', Square.onMouseUpListener);
document.addEventListener('mousemove', Square.onMouseMoveListener);
});
</script>
</body>
</html>
javascript html dom revealing-module-pattern
$endgroup$
add a comment |
$begingroup$
Feature requirements.
- I can move a square box.
- I can resize a square box by stretching four edges.
- Many square boxes can exist.
I've implemented moving and resizing square boxes by module pattern. Of course, I thought making a square box as a Class
. But I think that using module pattern is better in this case.
Module Pattern
- Pros
- Can manage only focused element. Don't need to care about unfocused elements.
- Can control event handling in it.
- Can hide private variables and methods.
- Cons
- Maybe need many changes by changing features.
- Test issue.
Class
- Pros
- Can manage and maintain properties of each square box.
- Can be more clean code.
- Cons
- Should manage many instances.
- Should add additional information to the DOM for finding each instance.
So, I implemented as module pattern. It looks like not good for test and can be more clear code. What should I do more for clean code or more testable?
https://jsfiddle.net/docf1t40/2/
<!DOCTYPE html>
<html>
<head>
<title>Practice</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.plate {
position: absolute;
width: 100%;
height: 100%;
background: rgb(240, 240, 240);
}
.edge {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgb(211, 211, 211);
}
.edge.lt {
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.edge.rt {
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.edge.lb {
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.edge.rb {
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
</style>
</head>
<body>
<script type="text/javascript">
const Square = (function() {
let userAction;
let focusedElement = {
DOM: null,
width: 0,
height: 0,
screenX: 0,
screenY: 0,
translateX: 0,
translateY: 0,
};
function focusElement(dom, width, height, sx, sy, tx, ty) {
focusedElement.DOM = dom;
focusedElement.width = width;
focusedElement.height = height;
focusedElement.screenX = sx;
focusedElement.screenY = sy;
focusedElement.translateX = tx;
focusedElement.translateY = ty;
}
function blurElement() {
focusedElement = {
DOM: null,
width: 0,
height: 0,
screenX: 0,
screenY: 0,
translateX: 0,
translateY: 0,
};
}
function getMovement(sx, sy) {
return {
x: sx - focusedElement.screenX,
y: sy - focusedElement.screenY
};
}
function move(sx, sy) {
const movement = getMovement(sx, sy);
const tx = focusedElement.translateX + movement.x;
const ty = focusedElement.translateY + movement.y;
focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
}
function resize(sx, sy) {
const movement = getMovement(sx, sy);
let tx = focusedElement.translateX;
let ty = focusedElement.translateY;
let width = focusedElement.width;
let height = focusedElement.height;
switch (userAction) {
case 'RESIZE-LT':
width = focusedElement.width - movement.x;
height = focusedElement.height - movement.y;
tx = focusedElement.translateX + movement.x;
ty = focusedElement.translateY + movement.y;
break;
case 'RESIZE-RT':
width = focusedElement.width + movement.x;
height = focusedElement.height - movement.y;
ty = focusedElement.translateY + movement.y;
break;
case 'RESIZE-LB':
width = focusedElement.width - movement.x;
height = focusedElement.height + movement.y;
tx = focusedElement.translateX + movement.x;
break;
case 'RESIZE-RB':
width = focusedElement.width + movement.x;
height = focusedElement.height + movement.y;
break;
}
width = Math.max(50, width);
height = Math.max(50, height);
focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
focusedElement.DOM.style.width = `${width}px`;
focusedElement.DOM.style.height = `${height}px`;
}
function onMouseDown(e) {
if (e.target && e.target.dataset && e.target.dataset.userAction) {
let tx = 0;
let ty = 0;
const transform = e.target.parentNode.style.transform;
const matchTranslate = transform.match(/translate((-?d+.?d*)px ?, ?(-?d+.?d*)px)/);
if (matchTranslate) {
tx = parseInt(matchTranslate[1]);
ty = parseInt(matchTranslate[2]);
}
focusElement(
e.target.parentNode,
parseInt(e.target.parentNode.style.width),
parseInt(e.target.parentNode.style.height),
e.screenX,
e.screenY,
tx,
ty
);
userAction = e.target.dataset.userAction;
}
}
function onMouseUp(e) {
blurElement();
userAction = null;
}
function onMouseMove(e) {
switch (userAction) {
case 'MOVE':
move(e.screenX, e.screenY);
break;
case 'RESIZE-LT':
case 'RESIZE-RT':
case 'RESIZE-LB':
case 'RESIZE-RB':
resize(e.screenX, e.screenY);
break;
}
}
return {
create: function(x, y, width, height) {
const div = document.createElement('div');
div.setAttribute('style', `position:absolute; width:${width}px; height:${height}px; transform: translate(${x}px, ${y}px)`);
div.innerHTML = `<div data-user-action="MOVE" class="plate"></div>
<span data-user-action="RESIZE-LT" class="edge lt"></span>
<span data-user-action="RESIZE-RT" class="edge rt"></span>
<span data-user-action="RESIZE-LB" class="edge lb"></span>
<span data-user-action="RESIZE-RB" class="edge rb"></span>`;
document.body.appendChild(div);
},
onMouseDownListener: onMouseDown,
onMouseUpListener: onMouseUp,
onMouseMoveListener: onMouseMove,
}
})();
document.addEventListener('DOMContentLoaded', function() {
Square.create(300, 300, 100, 200);
Square.create(200, 100, 80, 80);
document.addEventListener('mousedown', Square.onMouseDownListener);
document.addEventListener('mouseup', Square.onMouseUpListener);
document.addEventListener('mousemove', Square.onMouseMoveListener);
});
</script>
</body>
</html>
javascript html dom revealing-module-pattern
$endgroup$
Feature requirements.
- I can move a square box.
- I can resize a square box by stretching four edges.
- Many square boxes can exist.
I've implemented moving and resizing square boxes by module pattern. Of course, I thought making a square box as a Class
. But I think that using module pattern is better in this case.
Module Pattern
- Pros
- Can manage only focused element. Don't need to care about unfocused elements.
- Can control event handling in it.
- Can hide private variables and methods.
- Cons
- Maybe need many changes by changing features.
- Test issue.
Class
- Pros
- Can manage and maintain properties of each square box.
- Can be more clean code.
- Cons
- Should manage many instances.
- Should add additional information to the DOM for finding each instance.
So, I implemented as module pattern. It looks like not good for test and can be more clear code. What should I do more for clean code or more testable?
https://jsfiddle.net/docf1t40/2/
<!DOCTYPE html>
<html>
<head>
<title>Practice</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.plate {
position: absolute;
width: 100%;
height: 100%;
background: rgb(240, 240, 240);
}
.edge {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgb(211, 211, 211);
}
.edge.lt {
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.edge.rt {
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.edge.lb {
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.edge.rb {
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
</style>
</head>
<body>
<script type="text/javascript">
const Square = (function() {
let userAction;
let focusedElement = {
DOM: null,
width: 0,
height: 0,
screenX: 0,
screenY: 0,
translateX: 0,
translateY: 0,
};
function focusElement(dom, width, height, sx, sy, tx, ty) {
focusedElement.DOM = dom;
focusedElement.width = width;
focusedElement.height = height;
focusedElement.screenX = sx;
focusedElement.screenY = sy;
focusedElement.translateX = tx;
focusedElement.translateY = ty;
}
function blurElement() {
focusedElement = {
DOM: null,
width: 0,
height: 0,
screenX: 0,
screenY: 0,
translateX: 0,
translateY: 0,
};
}
function getMovement(sx, sy) {
return {
x: sx - focusedElement.screenX,
y: sy - focusedElement.screenY
};
}
function move(sx, sy) {
const movement = getMovement(sx, sy);
const tx = focusedElement.translateX + movement.x;
const ty = focusedElement.translateY + movement.y;
focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
}
function resize(sx, sy) {
const movement = getMovement(sx, sy);
let tx = focusedElement.translateX;
let ty = focusedElement.translateY;
let width = focusedElement.width;
let height = focusedElement.height;
switch (userAction) {
case 'RESIZE-LT':
width = focusedElement.width - movement.x;
height = focusedElement.height - movement.y;
tx = focusedElement.translateX + movement.x;
ty = focusedElement.translateY + movement.y;
break;
case 'RESIZE-RT':
width = focusedElement.width + movement.x;
height = focusedElement.height - movement.y;
ty = focusedElement.translateY + movement.y;
break;
case 'RESIZE-LB':
width = focusedElement.width - movement.x;
height = focusedElement.height + movement.y;
tx = focusedElement.translateX + movement.x;
break;
case 'RESIZE-RB':
width = focusedElement.width + movement.x;
height = focusedElement.height + movement.y;
break;
}
width = Math.max(50, width);
height = Math.max(50, height);
focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
focusedElement.DOM.style.width = `${width}px`;
focusedElement.DOM.style.height = `${height}px`;
}
function onMouseDown(e) {
if (e.target && e.target.dataset && e.target.dataset.userAction) {
let tx = 0;
let ty = 0;
const transform = e.target.parentNode.style.transform;
const matchTranslate = transform.match(/translate((-?d+.?d*)px ?, ?(-?d+.?d*)px)/);
if (matchTranslate) {
tx = parseInt(matchTranslate[1]);
ty = parseInt(matchTranslate[2]);
}
focusElement(
e.target.parentNode,
parseInt(e.target.parentNode.style.width),
parseInt(e.target.parentNode.style.height),
e.screenX,
e.screenY,
tx,
ty
);
userAction = e.target.dataset.userAction;
}
}
function onMouseUp(e) {
blurElement();
userAction = null;
}
function onMouseMove(e) {
switch (userAction) {
case 'MOVE':
move(e.screenX, e.screenY);
break;
case 'RESIZE-LT':
case 'RESIZE-RT':
case 'RESIZE-LB':
case 'RESIZE-RB':
resize(e.screenX, e.screenY);
break;
}
}
return {
create: function(x, y, width, height) {
const div = document.createElement('div');
div.setAttribute('style', `position:absolute; width:${width}px; height:${height}px; transform: translate(${x}px, ${y}px)`);
div.innerHTML = `<div data-user-action="MOVE" class="plate"></div>
<span data-user-action="RESIZE-LT" class="edge lt"></span>
<span data-user-action="RESIZE-RT" class="edge rt"></span>
<span data-user-action="RESIZE-LB" class="edge lb"></span>
<span data-user-action="RESIZE-RB" class="edge rb"></span>`;
document.body.appendChild(div);
},
onMouseDownListener: onMouseDown,
onMouseUpListener: onMouseUp,
onMouseMoveListener: onMouseMove,
}
})();
document.addEventListener('DOMContentLoaded', function() {
Square.create(300, 300, 100, 200);
Square.create(200, 100, 80, 80);
document.addEventListener('mousedown', Square.onMouseDownListener);
document.addEventListener('mouseup', Square.onMouseUpListener);
document.addEventListener('mousemove', Square.onMouseMoveListener);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Practice</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.plate {
position: absolute;
width: 100%;
height: 100%;
background: rgb(240, 240, 240);
}
.edge {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgb(211, 211, 211);
}
.edge.lt {
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.edge.rt {
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.edge.lb {
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.edge.rb {
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
</style>
</head>
<body>
<script type="text/javascript">
const Square = (function() {
let userAction;
let focusedElement = {
DOM: null,
width: 0,
height: 0,
screenX: 0,
screenY: 0,
translateX: 0,
translateY: 0,
};
function focusElement(dom, width, height, sx, sy, tx, ty) {
focusedElement.DOM = dom;
focusedElement.width = width;
focusedElement.height = height;
focusedElement.screenX = sx;
focusedElement.screenY = sy;
focusedElement.translateX = tx;
focusedElement.translateY = ty;
}
function blurElement() {
focusedElement = {
DOM: null,
width: 0,
height: 0,
screenX: 0,
screenY: 0,
translateX: 0,
translateY: 0,
};
}
function getMovement(sx, sy) {
return {
x: sx - focusedElement.screenX,
y: sy - focusedElement.screenY
};
}
function move(sx, sy) {
const movement = getMovement(sx, sy);
const tx = focusedElement.translateX + movement.x;
const ty = focusedElement.translateY + movement.y;
focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
}
function resize(sx, sy) {
const movement = getMovement(sx, sy);
let tx = focusedElement.translateX;
let ty = focusedElement.translateY;
let width = focusedElement.width;
let height = focusedElement.height;
switch (userAction) {
case 'RESIZE-LT':
width = focusedElement.width - movement.x;
height = focusedElement.height - movement.y;
tx = focusedElement.translateX + movement.x;
ty = focusedElement.translateY + movement.y;
break;
case 'RESIZE-RT':
width = focusedElement.width + movement.x;
height = focusedElement.height - movement.y;
ty = focusedElement.translateY + movement.y;
break;
case 'RESIZE-LB':
width = focusedElement.width - movement.x;
height = focusedElement.height + movement.y;
tx = focusedElement.translateX + movement.x;
break;
case 'RESIZE-RB':
width = focusedElement.width + movement.x;
height = focusedElement.height + movement.y;
break;
}
width = Math.max(50, width);
height = Math.max(50, height);
focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
focusedElement.DOM.style.width = `${width}px`;
focusedElement.DOM.style.height = `${height}px`;
}
function onMouseDown(e) {
if (e.target && e.target.dataset && e.target.dataset.userAction) {
let tx = 0;
let ty = 0;
const transform = e.target.parentNode.style.transform;
const matchTranslate = transform.match(/translate((-?d+.?d*)px ?, ?(-?d+.?d*)px)/);
if (matchTranslate) {
tx = parseInt(matchTranslate[1]);
ty = parseInt(matchTranslate[2]);
}
focusElement(
e.target.parentNode,
parseInt(e.target.parentNode.style.width),
parseInt(e.target.parentNode.style.height),
e.screenX,
e.screenY,
tx,
ty
);
userAction = e.target.dataset.userAction;
}
}
function onMouseUp(e) {
blurElement();
userAction = null;
}
function onMouseMove(e) {
switch (userAction) {
case 'MOVE':
move(e.screenX, e.screenY);
break;
case 'RESIZE-LT':
case 'RESIZE-RT':
case 'RESIZE-LB':
case 'RESIZE-RB':
resize(e.screenX, e.screenY);
break;
}
}
return {
create: function(x, y, width, height) {
const div = document.createElement('div');
div.setAttribute('style', `position:absolute; width:${width}px; height:${height}px; transform: translate(${x}px, ${y}px)`);
div.innerHTML = `<div data-user-action="MOVE" class="plate"></div>
<span data-user-action="RESIZE-LT" class="edge lt"></span>
<span data-user-action="RESIZE-RT" class="edge rt"></span>
<span data-user-action="RESIZE-LB" class="edge lb"></span>
<span data-user-action="RESIZE-RB" class="edge rb"></span>`;
document.body.appendChild(div);
},
onMouseDownListener: onMouseDown,
onMouseUpListener: onMouseUp,
onMouseMoveListener: onMouseMove,
}
})();
document.addEventListener('DOMContentLoaded', function() {
Square.create(300, 300, 100, 200);
Square.create(200, 100, 80, 80);
document.addEventListener('mousedown', Square.onMouseDownListener);
document.addEventListener('mouseup', Square.onMouseUpListener);
document.addEventListener('mousemove', Square.onMouseMoveListener);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Practice</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.plate {
position: absolute;
width: 100%;
height: 100%;
background: rgb(240, 240, 240);
}
.edge {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgb(211, 211, 211);
}
.edge.lt {
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.edge.rt {
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.edge.lb {
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.edge.rb {
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
</style>
</head>
<body>
<script type="text/javascript">
const Square = (function() {
let userAction;
let focusedElement = {
DOM: null,
width: 0,
height: 0,
screenX: 0,
screenY: 0,
translateX: 0,
translateY: 0,
};
function focusElement(dom, width, height, sx, sy, tx, ty) {
focusedElement.DOM = dom;
focusedElement.width = width;
focusedElement.height = height;
focusedElement.screenX = sx;
focusedElement.screenY = sy;
focusedElement.translateX = tx;
focusedElement.translateY = ty;
}
function blurElement() {
focusedElement = {
DOM: null,
width: 0,
height: 0,
screenX: 0,
screenY: 0,
translateX: 0,
translateY: 0,
};
}
function getMovement(sx, sy) {
return {
x: sx - focusedElement.screenX,
y: sy - focusedElement.screenY
};
}
function move(sx, sy) {
const movement = getMovement(sx, sy);
const tx = focusedElement.translateX + movement.x;
const ty = focusedElement.translateY + movement.y;
focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
}
function resize(sx, sy) {
const movement = getMovement(sx, sy);
let tx = focusedElement.translateX;
let ty = focusedElement.translateY;
let width = focusedElement.width;
let height = focusedElement.height;
switch (userAction) {
case 'RESIZE-LT':
width = focusedElement.width - movement.x;
height = focusedElement.height - movement.y;
tx = focusedElement.translateX + movement.x;
ty = focusedElement.translateY + movement.y;
break;
case 'RESIZE-RT':
width = focusedElement.width + movement.x;
height = focusedElement.height - movement.y;
ty = focusedElement.translateY + movement.y;
break;
case 'RESIZE-LB':
width = focusedElement.width - movement.x;
height = focusedElement.height + movement.y;
tx = focusedElement.translateX + movement.x;
break;
case 'RESIZE-RB':
width = focusedElement.width + movement.x;
height = focusedElement.height + movement.y;
break;
}
width = Math.max(50, width);
height = Math.max(50, height);
focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
focusedElement.DOM.style.width = `${width}px`;
focusedElement.DOM.style.height = `${height}px`;
}
function onMouseDown(e) {
if (e.target && e.target.dataset && e.target.dataset.userAction) {
let tx = 0;
let ty = 0;
const transform = e.target.parentNode.style.transform;
const matchTranslate = transform.match(/translate((-?d+.?d*)px ?, ?(-?d+.?d*)px)/);
if (matchTranslate) {
tx = parseInt(matchTranslate[1]);
ty = parseInt(matchTranslate[2]);
}
focusElement(
e.target.parentNode,
parseInt(e.target.parentNode.style.width),
parseInt(e.target.parentNode.style.height),
e.screenX,
e.screenY,
tx,
ty
);
userAction = e.target.dataset.userAction;
}
}
function onMouseUp(e) {
blurElement();
userAction = null;
}
function onMouseMove(e) {
switch (userAction) {
case 'MOVE':
move(e.screenX, e.screenY);
break;
case 'RESIZE-LT':
case 'RESIZE-RT':
case 'RESIZE-LB':
case 'RESIZE-RB':
resize(e.screenX, e.screenY);
break;
}
}
return {
create: function(x, y, width, height) {
const div = document.createElement('div');
div.setAttribute('style', `position:absolute; width:${width}px; height:${height}px; transform: translate(${x}px, ${y}px)`);
div.innerHTML = `<div data-user-action="MOVE" class="plate"></div>
<span data-user-action="RESIZE-LT" class="edge lt"></span>
<span data-user-action="RESIZE-RT" class="edge rt"></span>
<span data-user-action="RESIZE-LB" class="edge lb"></span>
<span data-user-action="RESIZE-RB" class="edge rb"></span>`;
document.body.appendChild(div);
},
onMouseDownListener: onMouseDown,
onMouseUpListener: onMouseUp,
onMouseMoveListener: onMouseMove,
}
})();
document.addEventListener('DOMContentLoaded', function() {
Square.create(300, 300, 100, 200);
Square.create(200, 100, 80, 80);
document.addEventListener('mousedown', Square.onMouseDownListener);
document.addEventListener('mouseup', Square.onMouseUpListener);
document.addEventListener('mousemove', Square.onMouseMoveListener);
});
</script>
</body>
</html>
javascript html dom revealing-module-pattern
javascript html dom revealing-module-pattern
edited Mar 14 at 2:01
200_success
130k17153419
130k17153419
asked Mar 14 at 1:47
gentlejogentlejo
1234
1234
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
$begingroup$
class
syntax
In JS class
is a syntax for creating objects. It is not an independent distinct entity. Class creates an object adding a constructor
function and assigning functions and (optionally) properties to the associated prototype. It does not provide any features not available using standard syntax.
The 'class` syntax does suffer from one serious flaw (that in my book makes it unusable). It lacks a mechanism to create private functions and properties. Objects created via the class syntax can not adequately encapsulate an objects state, an essential requirement of OO design.
Modules
In modern JS module, modules, modular, etc... refers to code that is import/export able.
Modules provide a local and isolated scope (state) that does provide some encapsulation for single instance objects created with the class
syntax. The same can be achieved with the IIS style and class
syntax.
Objects
Your code is a IIF, or IIFE meaning Immediately Invoked Function Expression const obj=(() => ({foo: "bar", log() {console.log(this.foo)}}))()
It provides the best encapsulation mechanism for single instance objects (such as your example)
A variation is an object factory function obj() { return {foo: "bar", log() {console.log(this.foo)}} }
best suited to long lived multi instance objects.
For performance and many (100 to 1000+) instances of short lived objects, the prototype model is best suited. function Obj() { this.foo = "bar" }; Obj.prototype = { log() {console.log(this.foo)}};
However the encapsulation model is not as strong.
Modern JS engines cache compiled code, thus you can define the prototype inside the function with only a minor memory penalty (prototyped properties and functions each need a reference per object) This lets you use closure to encapsulate state and still provide prototypal inheritance. However I would argue that in JS polymorphisum is the preferred method of extension for JS objects (though not everyone would agree).
There are many more ways to define and instanciate objects. Which you use is defined by how the object is to be used, and what your preferred style is.
Reviewing your code
The mouse event provides delta mouse position in
MouseEvent.movementX
andMouseEvent.movementY
so you don't need to functiongetMovement
You should not expose the mouse events for
Square
. HaveSquare.create
add the events if needed.You can simplify the resize and move logic by treating the corners as part of a side (top, bottom, left, and right) then you move only a side, left or right, top or bottom. Using a bitfield to define corners you can then specify which sides to move for each corner. (see example)
It is best not to add markup to the page. Use the DOM API to manipulate the DOM. I find the API somewhat awkward so use some helper functions to improve readability and reduce code noise. (see example
/* DOM helpers */
)Rather than inspecting the DOM element to get its position and size, assign your
focusElement
to each size-able element as you create it. Use aMap
to make the association. You can then just get that object using the element as key on mouse down (see example) Note that I assume your example is self contained and that there is no manipulation of relevant elements outside theSquare
objectYou update the DOM elements position in two places. Use a single function to update the position (and size) of the element.
To reduce code complexity use the
MouseEvent.type
property to determine the event type and handle all similar mouse events in one function.Mouse events are out of sync with the display refresh. For the cleanest updates you should be using
requestAnimationFrame
to update elements.requestAnimationFrame
ensures that backbuffers associated with elements that are rendered from within the callback are only presented during the displays vertical sync.
Example
Uses same IIF style to encapsulate the object (renamed Squares
as it can represent many)
Removed the 3 exposed onMouse...
functions.
The aim was to reduce source size and improve efficiency.
Creates a boxDesc
for each box mapped to the element, rather than updating the one focusElement
object.
I did not include the use of requestAnimationFrame
const Squares = (() => {
/* Privates */
var addMouseEvents = true, focus;
const boxMap = new Map();
const corners = { // bit fields
none: 0, // 0b0000
top: 1, // 0b0001
bottom: 2, // 0b0010
left: 4, // 0b0100
right: 8, // 0b1000
};
const MIN_SIZE = 50;
const HANDLE_SIZE = 8; // adds 2 px
/* DOM helpers */
const tag = (name, props ={}) => Object.assign(document.createElement(name), props);
const elStyle = (el, style) => (Object.assign(el.style, style), el);
const elData = (el, data) => (Object.assign(el.dataset, data), el);
const append = (par, ...sibs) => {
for(const sib of sibs) { par.appendChild(sib) }
return par;
};
const boxDesc = (DOM, width, height, x, y) => ({ DOM, width, height, x, y });
function update(box) {
box.width = box.width < MIN_SIZE ? MIN_SIZE : box.width;
box.height = box.height < MIN_SIZE ? MIN_SIZE : box.height;
const right = innerWidth - (box.width + HANDLE_SIZE);
const bot = innerHeight - (box.height + HANDLE_SIZE);
box.x = box.x < HANDLE_SIZE ? HANDLE_SIZE : box.x > right ? right : box.x;
box.y = box.y < HANDLE_SIZE ? HANDLE_SIZE : box.y > bot ? bot : box.y;
elStyle(box.DOM, {
transform: `translate(${box.x}px, ${box.y}px)`,
width: box.width + "px",
height: box.height + "px",
});
return box;
}
function move(box, dx, dy) {
const bot = innerHeight - HANDLE_SIZE;
const right = innerWidth - HANDLE_SIZE;
if (box.action === corners.none) {
box.x += dx;
box.y += dy;
} else {
if ((box.action & corners.bottom) === corners.bottom) {
box.height = box.y + box.height + dy > bot ? bot - box.y : box.height + dy;
} else {
if (box.height - dy < MIN_SIZE) { dy = box.height - MIN_SIZE }
if (box.y + dy < HANDLE_SIZE) { dy = HANDLE_SIZE - box.y }
box.y += dy;
box.height -= dy;
}
if ((box.action & corners.right) === corners.right) {
box.width = box.x + box.width + dx > right ? right - box.x : box.width + dx;
} else {
if (box.width - dx < MIN_SIZE) { dx = box.width - MIN_SIZE }
if (box.x + dx < HANDLE_SIZE) { dx = HANDLE_SIZE - box.x }
box.x += dx;
box.width -= dx;
}
}
update(box);
}
function mouseEvent(e) {
if(e.type === "mousedown") {
if (e.target.dataset && e.target.dataset.userAction) {
focus = boxMap.get(e.target.parentNode);
focus.action = Number(e.target.dataset.userAction);
}
}else if(e.type === "mouseup") { focus = undefined }
else {
if (e.buttons === 0) { focus = undefined } // to stop sticky button in snippet
if (focus) { move(focus, e.movementX, e.movementY) }
}
}
function mouseEvents() {
document.addEventListener('mousedown', mouseEvent);
document.addEventListener('mouseup', mouseEvent);
document.addEventListener('mousemove', mouseEvent);
addMouseEvents = false;
}
return {
create(x, y, width, height) {
const box = append(
elStyle(tag("div"), { position: "absolute" }),
elData(tag("div", {className : "plate"}), {userAction : corners.none}),
elData(tag("span", {className : "edge lt"}), {userAction : corners.top + corners.left}),
elData(tag("span", {className : "edge rt"}), {userAction : corners.top + corners.right}),
elData(tag("span", {className : "edge lb"}), {userAction : corners.bottom + corners.left}),
elData(tag("span", {className : "edge rb"}), {userAction : corners.bottom + corners.right})
);
boxMap.set(box, update(boxDesc(box, width, height, x, y))); // update() sizes and positions elements
append(document.body, box);
if (addMouseEvents) { mouseEvents() }
}
};
})();
document.addEventListener('DOMContentLoaded', function() {
Squares.create(10, 100, 80, 80);
Squares.create(110, 100, 80, 80);
Squares.create(210, 100, 80, 80);
Squares.create(310, 100, 80, 80);
Squares.create(410, 100, 80, 80);
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
-moz-user-select: none;
}
.plate {
position: absolute;
width: 100%;
height: 100%;
background: rgb(240, 240, 240);
cursor: move;
}
.plate:hover {
background: #DEE;
}
.edge {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgb(211, 211, 211);
}
.edge:hover {
background: #CDD;
}
.edge.lt {
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.edge.rt {
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.edge.lb {
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.edge.rb {
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
$endgroup$
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f215388%2fmove-and-resize-square-boxes-with-vanilla-js%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
class
syntax
In JS class
is a syntax for creating objects. It is not an independent distinct entity. Class creates an object adding a constructor
function and assigning functions and (optionally) properties to the associated prototype. It does not provide any features not available using standard syntax.
The 'class` syntax does suffer from one serious flaw (that in my book makes it unusable). It lacks a mechanism to create private functions and properties. Objects created via the class syntax can not adequately encapsulate an objects state, an essential requirement of OO design.
Modules
In modern JS module, modules, modular, etc... refers to code that is import/export able.
Modules provide a local and isolated scope (state) that does provide some encapsulation for single instance objects created with the class
syntax. The same can be achieved with the IIS style and class
syntax.
Objects
Your code is a IIF, or IIFE meaning Immediately Invoked Function Expression const obj=(() => ({foo: "bar", log() {console.log(this.foo)}}))()
It provides the best encapsulation mechanism for single instance objects (such as your example)
A variation is an object factory function obj() { return {foo: "bar", log() {console.log(this.foo)}} }
best suited to long lived multi instance objects.
For performance and many (100 to 1000+) instances of short lived objects, the prototype model is best suited. function Obj() { this.foo = "bar" }; Obj.prototype = { log() {console.log(this.foo)}};
However the encapsulation model is not as strong.
Modern JS engines cache compiled code, thus you can define the prototype inside the function with only a minor memory penalty (prototyped properties and functions each need a reference per object) This lets you use closure to encapsulate state and still provide prototypal inheritance. However I would argue that in JS polymorphisum is the preferred method of extension for JS objects (though not everyone would agree).
There are many more ways to define and instanciate objects. Which you use is defined by how the object is to be used, and what your preferred style is.
Reviewing your code
The mouse event provides delta mouse position in
MouseEvent.movementX
andMouseEvent.movementY
so you don't need to functiongetMovement
You should not expose the mouse events for
Square
. HaveSquare.create
add the events if needed.You can simplify the resize and move logic by treating the corners as part of a side (top, bottom, left, and right) then you move only a side, left or right, top or bottom. Using a bitfield to define corners you can then specify which sides to move for each corner. (see example)
It is best not to add markup to the page. Use the DOM API to manipulate the DOM. I find the API somewhat awkward so use some helper functions to improve readability and reduce code noise. (see example
/* DOM helpers */
)Rather than inspecting the DOM element to get its position and size, assign your
focusElement
to each size-able element as you create it. Use aMap
to make the association. You can then just get that object using the element as key on mouse down (see example) Note that I assume your example is self contained and that there is no manipulation of relevant elements outside theSquare
objectYou update the DOM elements position in two places. Use a single function to update the position (and size) of the element.
To reduce code complexity use the
MouseEvent.type
property to determine the event type and handle all similar mouse events in one function.Mouse events are out of sync with the display refresh. For the cleanest updates you should be using
requestAnimationFrame
to update elements.requestAnimationFrame
ensures that backbuffers associated with elements that are rendered from within the callback are only presented during the displays vertical sync.
Example
Uses same IIF style to encapsulate the object (renamed Squares
as it can represent many)
Removed the 3 exposed onMouse...
functions.
The aim was to reduce source size and improve efficiency.
Creates a boxDesc
for each box mapped to the element, rather than updating the one focusElement
object.
I did not include the use of requestAnimationFrame
const Squares = (() => {
/* Privates */
var addMouseEvents = true, focus;
const boxMap = new Map();
const corners = { // bit fields
none: 0, // 0b0000
top: 1, // 0b0001
bottom: 2, // 0b0010
left: 4, // 0b0100
right: 8, // 0b1000
};
const MIN_SIZE = 50;
const HANDLE_SIZE = 8; // adds 2 px
/* DOM helpers */
const tag = (name, props ={}) => Object.assign(document.createElement(name), props);
const elStyle = (el, style) => (Object.assign(el.style, style), el);
const elData = (el, data) => (Object.assign(el.dataset, data), el);
const append = (par, ...sibs) => {
for(const sib of sibs) { par.appendChild(sib) }
return par;
};
const boxDesc = (DOM, width, height, x, y) => ({ DOM, width, height, x, y });
function update(box) {
box.width = box.width < MIN_SIZE ? MIN_SIZE : box.width;
box.height = box.height < MIN_SIZE ? MIN_SIZE : box.height;
const right = innerWidth - (box.width + HANDLE_SIZE);
const bot = innerHeight - (box.height + HANDLE_SIZE);
box.x = box.x < HANDLE_SIZE ? HANDLE_SIZE : box.x > right ? right : box.x;
box.y = box.y < HANDLE_SIZE ? HANDLE_SIZE : box.y > bot ? bot : box.y;
elStyle(box.DOM, {
transform: `translate(${box.x}px, ${box.y}px)`,
width: box.width + "px",
height: box.height + "px",
});
return box;
}
function move(box, dx, dy) {
const bot = innerHeight - HANDLE_SIZE;
const right = innerWidth - HANDLE_SIZE;
if (box.action === corners.none) {
box.x += dx;
box.y += dy;
} else {
if ((box.action & corners.bottom) === corners.bottom) {
box.height = box.y + box.height + dy > bot ? bot - box.y : box.height + dy;
} else {
if (box.height - dy < MIN_SIZE) { dy = box.height - MIN_SIZE }
if (box.y + dy < HANDLE_SIZE) { dy = HANDLE_SIZE - box.y }
box.y += dy;
box.height -= dy;
}
if ((box.action & corners.right) === corners.right) {
box.width = box.x + box.width + dx > right ? right - box.x : box.width + dx;
} else {
if (box.width - dx < MIN_SIZE) { dx = box.width - MIN_SIZE }
if (box.x + dx < HANDLE_SIZE) { dx = HANDLE_SIZE - box.x }
box.x += dx;
box.width -= dx;
}
}
update(box);
}
function mouseEvent(e) {
if(e.type === "mousedown") {
if (e.target.dataset && e.target.dataset.userAction) {
focus = boxMap.get(e.target.parentNode);
focus.action = Number(e.target.dataset.userAction);
}
}else if(e.type === "mouseup") { focus = undefined }
else {
if (e.buttons === 0) { focus = undefined } // to stop sticky button in snippet
if (focus) { move(focus, e.movementX, e.movementY) }
}
}
function mouseEvents() {
document.addEventListener('mousedown', mouseEvent);
document.addEventListener('mouseup', mouseEvent);
document.addEventListener('mousemove', mouseEvent);
addMouseEvents = false;
}
return {
create(x, y, width, height) {
const box = append(
elStyle(tag("div"), { position: "absolute" }),
elData(tag("div", {className : "plate"}), {userAction : corners.none}),
elData(tag("span", {className : "edge lt"}), {userAction : corners.top + corners.left}),
elData(tag("span", {className : "edge rt"}), {userAction : corners.top + corners.right}),
elData(tag("span", {className : "edge lb"}), {userAction : corners.bottom + corners.left}),
elData(tag("span", {className : "edge rb"}), {userAction : corners.bottom + corners.right})
);
boxMap.set(box, update(boxDesc(box, width, height, x, y))); // update() sizes and positions elements
append(document.body, box);
if (addMouseEvents) { mouseEvents() }
}
};
})();
document.addEventListener('DOMContentLoaded', function() {
Squares.create(10, 100, 80, 80);
Squares.create(110, 100, 80, 80);
Squares.create(210, 100, 80, 80);
Squares.create(310, 100, 80, 80);
Squares.create(410, 100, 80, 80);
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
-moz-user-select: none;
}
.plate {
position: absolute;
width: 100%;
height: 100%;
background: rgb(240, 240, 240);
cursor: move;
}
.plate:hover {
background: #DEE;
}
.edge {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgb(211, 211, 211);
}
.edge:hover {
background: #CDD;
}
.edge.lt {
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.edge.rt {
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.edge.lb {
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.edge.rb {
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
$endgroup$
add a comment |
$begingroup$
class
syntax
In JS class
is a syntax for creating objects. It is not an independent distinct entity. Class creates an object adding a constructor
function and assigning functions and (optionally) properties to the associated prototype. It does not provide any features not available using standard syntax.
The 'class` syntax does suffer from one serious flaw (that in my book makes it unusable). It lacks a mechanism to create private functions and properties. Objects created via the class syntax can not adequately encapsulate an objects state, an essential requirement of OO design.
Modules
In modern JS module, modules, modular, etc... refers to code that is import/export able.
Modules provide a local and isolated scope (state) that does provide some encapsulation for single instance objects created with the class
syntax. The same can be achieved with the IIS style and class
syntax.
Objects
Your code is a IIF, or IIFE meaning Immediately Invoked Function Expression const obj=(() => ({foo: "bar", log() {console.log(this.foo)}}))()
It provides the best encapsulation mechanism for single instance objects (such as your example)
A variation is an object factory function obj() { return {foo: "bar", log() {console.log(this.foo)}} }
best suited to long lived multi instance objects.
For performance and many (100 to 1000+) instances of short lived objects, the prototype model is best suited. function Obj() { this.foo = "bar" }; Obj.prototype = { log() {console.log(this.foo)}};
However the encapsulation model is not as strong.
Modern JS engines cache compiled code, thus you can define the prototype inside the function with only a minor memory penalty (prototyped properties and functions each need a reference per object) This lets you use closure to encapsulate state and still provide prototypal inheritance. However I would argue that in JS polymorphisum is the preferred method of extension for JS objects (though not everyone would agree).
There are many more ways to define and instanciate objects. Which you use is defined by how the object is to be used, and what your preferred style is.
Reviewing your code
The mouse event provides delta mouse position in
MouseEvent.movementX
andMouseEvent.movementY
so you don't need to functiongetMovement
You should not expose the mouse events for
Square
. HaveSquare.create
add the events if needed.You can simplify the resize and move logic by treating the corners as part of a side (top, bottom, left, and right) then you move only a side, left or right, top or bottom. Using a bitfield to define corners you can then specify which sides to move for each corner. (see example)
It is best not to add markup to the page. Use the DOM API to manipulate the DOM. I find the API somewhat awkward so use some helper functions to improve readability and reduce code noise. (see example
/* DOM helpers */
)Rather than inspecting the DOM element to get its position and size, assign your
focusElement
to each size-able element as you create it. Use aMap
to make the association. You can then just get that object using the element as key on mouse down (see example) Note that I assume your example is self contained and that there is no manipulation of relevant elements outside theSquare
objectYou update the DOM elements position in two places. Use a single function to update the position (and size) of the element.
To reduce code complexity use the
MouseEvent.type
property to determine the event type and handle all similar mouse events in one function.Mouse events are out of sync with the display refresh. For the cleanest updates you should be using
requestAnimationFrame
to update elements.requestAnimationFrame
ensures that backbuffers associated with elements that are rendered from within the callback are only presented during the displays vertical sync.
Example
Uses same IIF style to encapsulate the object (renamed Squares
as it can represent many)
Removed the 3 exposed onMouse...
functions.
The aim was to reduce source size and improve efficiency.
Creates a boxDesc
for each box mapped to the element, rather than updating the one focusElement
object.
I did not include the use of requestAnimationFrame
const Squares = (() => {
/* Privates */
var addMouseEvents = true, focus;
const boxMap = new Map();
const corners = { // bit fields
none: 0, // 0b0000
top: 1, // 0b0001
bottom: 2, // 0b0010
left: 4, // 0b0100
right: 8, // 0b1000
};
const MIN_SIZE = 50;
const HANDLE_SIZE = 8; // adds 2 px
/* DOM helpers */
const tag = (name, props ={}) => Object.assign(document.createElement(name), props);
const elStyle = (el, style) => (Object.assign(el.style, style), el);
const elData = (el, data) => (Object.assign(el.dataset, data), el);
const append = (par, ...sibs) => {
for(const sib of sibs) { par.appendChild(sib) }
return par;
};
const boxDesc = (DOM, width, height, x, y) => ({ DOM, width, height, x, y });
function update(box) {
box.width = box.width < MIN_SIZE ? MIN_SIZE : box.width;
box.height = box.height < MIN_SIZE ? MIN_SIZE : box.height;
const right = innerWidth - (box.width + HANDLE_SIZE);
const bot = innerHeight - (box.height + HANDLE_SIZE);
box.x = box.x < HANDLE_SIZE ? HANDLE_SIZE : box.x > right ? right : box.x;
box.y = box.y < HANDLE_SIZE ? HANDLE_SIZE : box.y > bot ? bot : box.y;
elStyle(box.DOM, {
transform: `translate(${box.x}px, ${box.y}px)`,
width: box.width + "px",
height: box.height + "px",
});
return box;
}
function move(box, dx, dy) {
const bot = innerHeight - HANDLE_SIZE;
const right = innerWidth - HANDLE_SIZE;
if (box.action === corners.none) {
box.x += dx;
box.y += dy;
} else {
if ((box.action & corners.bottom) === corners.bottom) {
box.height = box.y + box.height + dy > bot ? bot - box.y : box.height + dy;
} else {
if (box.height - dy < MIN_SIZE) { dy = box.height - MIN_SIZE }
if (box.y + dy < HANDLE_SIZE) { dy = HANDLE_SIZE - box.y }
box.y += dy;
box.height -= dy;
}
if ((box.action & corners.right) === corners.right) {
box.width = box.x + box.width + dx > right ? right - box.x : box.width + dx;
} else {
if (box.width - dx < MIN_SIZE) { dx = box.width - MIN_SIZE }
if (box.x + dx < HANDLE_SIZE) { dx = HANDLE_SIZE - box.x }
box.x += dx;
box.width -= dx;
}
}
update(box);
}
function mouseEvent(e) {
if(e.type === "mousedown") {
if (e.target.dataset && e.target.dataset.userAction) {
focus = boxMap.get(e.target.parentNode);
focus.action = Number(e.target.dataset.userAction);
}
}else if(e.type === "mouseup") { focus = undefined }
else {
if (e.buttons === 0) { focus = undefined } // to stop sticky button in snippet
if (focus) { move(focus, e.movementX, e.movementY) }
}
}
function mouseEvents() {
document.addEventListener('mousedown', mouseEvent);
document.addEventListener('mouseup', mouseEvent);
document.addEventListener('mousemove', mouseEvent);
addMouseEvents = false;
}
return {
create(x, y, width, height) {
const box = append(
elStyle(tag("div"), { position: "absolute" }),
elData(tag("div", {className : "plate"}), {userAction : corners.none}),
elData(tag("span", {className : "edge lt"}), {userAction : corners.top + corners.left}),
elData(tag("span", {className : "edge rt"}), {userAction : corners.top + corners.right}),
elData(tag("span", {className : "edge lb"}), {userAction : corners.bottom + corners.left}),
elData(tag("span", {className : "edge rb"}), {userAction : corners.bottom + corners.right})
);
boxMap.set(box, update(boxDesc(box, width, height, x, y))); // update() sizes and positions elements
append(document.body, box);
if (addMouseEvents) { mouseEvents() }
}
};
})();
document.addEventListener('DOMContentLoaded', function() {
Squares.create(10, 100, 80, 80);
Squares.create(110, 100, 80, 80);
Squares.create(210, 100, 80, 80);
Squares.create(310, 100, 80, 80);
Squares.create(410, 100, 80, 80);
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
-moz-user-select: none;
}
.plate {
position: absolute;
width: 100%;
height: 100%;
background: rgb(240, 240, 240);
cursor: move;
}
.plate:hover {
background: #DEE;
}
.edge {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgb(211, 211, 211);
}
.edge:hover {
background: #CDD;
}
.edge.lt {
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.edge.rt {
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.edge.lb {
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.edge.rb {
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
$endgroup$
add a comment |
$begingroup$
class
syntax
In JS class
is a syntax for creating objects. It is not an independent distinct entity. Class creates an object adding a constructor
function and assigning functions and (optionally) properties to the associated prototype. It does not provide any features not available using standard syntax.
The 'class` syntax does suffer from one serious flaw (that in my book makes it unusable). It lacks a mechanism to create private functions and properties. Objects created via the class syntax can not adequately encapsulate an objects state, an essential requirement of OO design.
Modules
In modern JS module, modules, modular, etc... refers to code that is import/export able.
Modules provide a local and isolated scope (state) that does provide some encapsulation for single instance objects created with the class
syntax. The same can be achieved with the IIS style and class
syntax.
Objects
Your code is a IIF, or IIFE meaning Immediately Invoked Function Expression const obj=(() => ({foo: "bar", log() {console.log(this.foo)}}))()
It provides the best encapsulation mechanism for single instance objects (such as your example)
A variation is an object factory function obj() { return {foo: "bar", log() {console.log(this.foo)}} }
best suited to long lived multi instance objects.
For performance and many (100 to 1000+) instances of short lived objects, the prototype model is best suited. function Obj() { this.foo = "bar" }; Obj.prototype = { log() {console.log(this.foo)}};
However the encapsulation model is not as strong.
Modern JS engines cache compiled code, thus you can define the prototype inside the function with only a minor memory penalty (prototyped properties and functions each need a reference per object) This lets you use closure to encapsulate state and still provide prototypal inheritance. However I would argue that in JS polymorphisum is the preferred method of extension for JS objects (though not everyone would agree).
There are many more ways to define and instanciate objects. Which you use is defined by how the object is to be used, and what your preferred style is.
Reviewing your code
The mouse event provides delta mouse position in
MouseEvent.movementX
andMouseEvent.movementY
so you don't need to functiongetMovement
You should not expose the mouse events for
Square
. HaveSquare.create
add the events if needed.You can simplify the resize and move logic by treating the corners as part of a side (top, bottom, left, and right) then you move only a side, left or right, top or bottom. Using a bitfield to define corners you can then specify which sides to move for each corner. (see example)
It is best not to add markup to the page. Use the DOM API to manipulate the DOM. I find the API somewhat awkward so use some helper functions to improve readability and reduce code noise. (see example
/* DOM helpers */
)Rather than inspecting the DOM element to get its position and size, assign your
focusElement
to each size-able element as you create it. Use aMap
to make the association. You can then just get that object using the element as key on mouse down (see example) Note that I assume your example is self contained and that there is no manipulation of relevant elements outside theSquare
objectYou update the DOM elements position in two places. Use a single function to update the position (and size) of the element.
To reduce code complexity use the
MouseEvent.type
property to determine the event type and handle all similar mouse events in one function.Mouse events are out of sync with the display refresh. For the cleanest updates you should be using
requestAnimationFrame
to update elements.requestAnimationFrame
ensures that backbuffers associated with elements that are rendered from within the callback are only presented during the displays vertical sync.
Example
Uses same IIF style to encapsulate the object (renamed Squares
as it can represent many)
Removed the 3 exposed onMouse...
functions.
The aim was to reduce source size and improve efficiency.
Creates a boxDesc
for each box mapped to the element, rather than updating the one focusElement
object.
I did not include the use of requestAnimationFrame
const Squares = (() => {
/* Privates */
var addMouseEvents = true, focus;
const boxMap = new Map();
const corners = { // bit fields
none: 0, // 0b0000
top: 1, // 0b0001
bottom: 2, // 0b0010
left: 4, // 0b0100
right: 8, // 0b1000
};
const MIN_SIZE = 50;
const HANDLE_SIZE = 8; // adds 2 px
/* DOM helpers */
const tag = (name, props ={}) => Object.assign(document.createElement(name), props);
const elStyle = (el, style) => (Object.assign(el.style, style), el);
const elData = (el, data) => (Object.assign(el.dataset, data), el);
const append = (par, ...sibs) => {
for(const sib of sibs) { par.appendChild(sib) }
return par;
};
const boxDesc = (DOM, width, height, x, y) => ({ DOM, width, height, x, y });
function update(box) {
box.width = box.width < MIN_SIZE ? MIN_SIZE : box.width;
box.height = box.height < MIN_SIZE ? MIN_SIZE : box.height;
const right = innerWidth - (box.width + HANDLE_SIZE);
const bot = innerHeight - (box.height + HANDLE_SIZE);
box.x = box.x < HANDLE_SIZE ? HANDLE_SIZE : box.x > right ? right : box.x;
box.y = box.y < HANDLE_SIZE ? HANDLE_SIZE : box.y > bot ? bot : box.y;
elStyle(box.DOM, {
transform: `translate(${box.x}px, ${box.y}px)`,
width: box.width + "px",
height: box.height + "px",
});
return box;
}
function move(box, dx, dy) {
const bot = innerHeight - HANDLE_SIZE;
const right = innerWidth - HANDLE_SIZE;
if (box.action === corners.none) {
box.x += dx;
box.y += dy;
} else {
if ((box.action & corners.bottom) === corners.bottom) {
box.height = box.y + box.height + dy > bot ? bot - box.y : box.height + dy;
} else {
if (box.height - dy < MIN_SIZE) { dy = box.height - MIN_SIZE }
if (box.y + dy < HANDLE_SIZE) { dy = HANDLE_SIZE - box.y }
box.y += dy;
box.height -= dy;
}
if ((box.action & corners.right) === corners.right) {
box.width = box.x + box.width + dx > right ? right - box.x : box.width + dx;
} else {
if (box.width - dx < MIN_SIZE) { dx = box.width - MIN_SIZE }
if (box.x + dx < HANDLE_SIZE) { dx = HANDLE_SIZE - box.x }
box.x += dx;
box.width -= dx;
}
}
update(box);
}
function mouseEvent(e) {
if(e.type === "mousedown") {
if (e.target.dataset && e.target.dataset.userAction) {
focus = boxMap.get(e.target.parentNode);
focus.action = Number(e.target.dataset.userAction);
}
}else if(e.type === "mouseup") { focus = undefined }
else {
if (e.buttons === 0) { focus = undefined } // to stop sticky button in snippet
if (focus) { move(focus, e.movementX, e.movementY) }
}
}
function mouseEvents() {
document.addEventListener('mousedown', mouseEvent);
document.addEventListener('mouseup', mouseEvent);
document.addEventListener('mousemove', mouseEvent);
addMouseEvents = false;
}
return {
create(x, y, width, height) {
const box = append(
elStyle(tag("div"), { position: "absolute" }),
elData(tag("div", {className : "plate"}), {userAction : corners.none}),
elData(tag("span", {className : "edge lt"}), {userAction : corners.top + corners.left}),
elData(tag("span", {className : "edge rt"}), {userAction : corners.top + corners.right}),
elData(tag("span", {className : "edge lb"}), {userAction : corners.bottom + corners.left}),
elData(tag("span", {className : "edge rb"}), {userAction : corners.bottom + corners.right})
);
boxMap.set(box, update(boxDesc(box, width, height, x, y))); // update() sizes and positions elements
append(document.body, box);
if (addMouseEvents) { mouseEvents() }
}
};
})();
document.addEventListener('DOMContentLoaded', function() {
Squares.create(10, 100, 80, 80);
Squares.create(110, 100, 80, 80);
Squares.create(210, 100, 80, 80);
Squares.create(310, 100, 80, 80);
Squares.create(410, 100, 80, 80);
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
-moz-user-select: none;
}
.plate {
position: absolute;
width: 100%;
height: 100%;
background: rgb(240, 240, 240);
cursor: move;
}
.plate:hover {
background: #DEE;
}
.edge {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgb(211, 211, 211);
}
.edge:hover {
background: #CDD;
}
.edge.lt {
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.edge.rt {
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.edge.lb {
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.edge.rb {
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
$endgroup$
class
syntax
In JS class
is a syntax for creating objects. It is not an independent distinct entity. Class creates an object adding a constructor
function and assigning functions and (optionally) properties to the associated prototype. It does not provide any features not available using standard syntax.
The 'class` syntax does suffer from one serious flaw (that in my book makes it unusable). It lacks a mechanism to create private functions and properties. Objects created via the class syntax can not adequately encapsulate an objects state, an essential requirement of OO design.
Modules
In modern JS module, modules, modular, etc... refers to code that is import/export able.
Modules provide a local and isolated scope (state) that does provide some encapsulation for single instance objects created with the class
syntax. The same can be achieved with the IIS style and class
syntax.
Objects
Your code is a IIF, or IIFE meaning Immediately Invoked Function Expression const obj=(() => ({foo: "bar", log() {console.log(this.foo)}}))()
It provides the best encapsulation mechanism for single instance objects (such as your example)
A variation is an object factory function obj() { return {foo: "bar", log() {console.log(this.foo)}} }
best suited to long lived multi instance objects.
For performance and many (100 to 1000+) instances of short lived objects, the prototype model is best suited. function Obj() { this.foo = "bar" }; Obj.prototype = { log() {console.log(this.foo)}};
However the encapsulation model is not as strong.
Modern JS engines cache compiled code, thus you can define the prototype inside the function with only a minor memory penalty (prototyped properties and functions each need a reference per object) This lets you use closure to encapsulate state and still provide prototypal inheritance. However I would argue that in JS polymorphisum is the preferred method of extension for JS objects (though not everyone would agree).
There are many more ways to define and instanciate objects. Which you use is defined by how the object is to be used, and what your preferred style is.
Reviewing your code
The mouse event provides delta mouse position in
MouseEvent.movementX
andMouseEvent.movementY
so you don't need to functiongetMovement
You should not expose the mouse events for
Square
. HaveSquare.create
add the events if needed.You can simplify the resize and move logic by treating the corners as part of a side (top, bottom, left, and right) then you move only a side, left or right, top or bottom. Using a bitfield to define corners you can then specify which sides to move for each corner. (see example)
It is best not to add markup to the page. Use the DOM API to manipulate the DOM. I find the API somewhat awkward so use some helper functions to improve readability and reduce code noise. (see example
/* DOM helpers */
)Rather than inspecting the DOM element to get its position and size, assign your
focusElement
to each size-able element as you create it. Use aMap
to make the association. You can then just get that object using the element as key on mouse down (see example) Note that I assume your example is self contained and that there is no manipulation of relevant elements outside theSquare
objectYou update the DOM elements position in two places. Use a single function to update the position (and size) of the element.
To reduce code complexity use the
MouseEvent.type
property to determine the event type and handle all similar mouse events in one function.Mouse events are out of sync with the display refresh. For the cleanest updates you should be using
requestAnimationFrame
to update elements.requestAnimationFrame
ensures that backbuffers associated with elements that are rendered from within the callback are only presented during the displays vertical sync.
Example
Uses same IIF style to encapsulate the object (renamed Squares
as it can represent many)
Removed the 3 exposed onMouse...
functions.
The aim was to reduce source size and improve efficiency.
Creates a boxDesc
for each box mapped to the element, rather than updating the one focusElement
object.
I did not include the use of requestAnimationFrame
const Squares = (() => {
/* Privates */
var addMouseEvents = true, focus;
const boxMap = new Map();
const corners = { // bit fields
none: 0, // 0b0000
top: 1, // 0b0001
bottom: 2, // 0b0010
left: 4, // 0b0100
right: 8, // 0b1000
};
const MIN_SIZE = 50;
const HANDLE_SIZE = 8; // adds 2 px
/* DOM helpers */
const tag = (name, props ={}) => Object.assign(document.createElement(name), props);
const elStyle = (el, style) => (Object.assign(el.style, style), el);
const elData = (el, data) => (Object.assign(el.dataset, data), el);
const append = (par, ...sibs) => {
for(const sib of sibs) { par.appendChild(sib) }
return par;
};
const boxDesc = (DOM, width, height, x, y) => ({ DOM, width, height, x, y });
function update(box) {
box.width = box.width < MIN_SIZE ? MIN_SIZE : box.width;
box.height = box.height < MIN_SIZE ? MIN_SIZE : box.height;
const right = innerWidth - (box.width + HANDLE_SIZE);
const bot = innerHeight - (box.height + HANDLE_SIZE);
box.x = box.x < HANDLE_SIZE ? HANDLE_SIZE : box.x > right ? right : box.x;
box.y = box.y < HANDLE_SIZE ? HANDLE_SIZE : box.y > bot ? bot : box.y;
elStyle(box.DOM, {
transform: `translate(${box.x}px, ${box.y}px)`,
width: box.width + "px",
height: box.height + "px",
});
return box;
}
function move(box, dx, dy) {
const bot = innerHeight - HANDLE_SIZE;
const right = innerWidth - HANDLE_SIZE;
if (box.action === corners.none) {
box.x += dx;
box.y += dy;
} else {
if ((box.action & corners.bottom) === corners.bottom) {
box.height = box.y + box.height + dy > bot ? bot - box.y : box.height + dy;
} else {
if (box.height - dy < MIN_SIZE) { dy = box.height - MIN_SIZE }
if (box.y + dy < HANDLE_SIZE) { dy = HANDLE_SIZE - box.y }
box.y += dy;
box.height -= dy;
}
if ((box.action & corners.right) === corners.right) {
box.width = box.x + box.width + dx > right ? right - box.x : box.width + dx;
} else {
if (box.width - dx < MIN_SIZE) { dx = box.width - MIN_SIZE }
if (box.x + dx < HANDLE_SIZE) { dx = HANDLE_SIZE - box.x }
box.x += dx;
box.width -= dx;
}
}
update(box);
}
function mouseEvent(e) {
if(e.type === "mousedown") {
if (e.target.dataset && e.target.dataset.userAction) {
focus = boxMap.get(e.target.parentNode);
focus.action = Number(e.target.dataset.userAction);
}
}else if(e.type === "mouseup") { focus = undefined }
else {
if (e.buttons === 0) { focus = undefined } // to stop sticky button in snippet
if (focus) { move(focus, e.movementX, e.movementY) }
}
}
function mouseEvents() {
document.addEventListener('mousedown', mouseEvent);
document.addEventListener('mouseup', mouseEvent);
document.addEventListener('mousemove', mouseEvent);
addMouseEvents = false;
}
return {
create(x, y, width, height) {
const box = append(
elStyle(tag("div"), { position: "absolute" }),
elData(tag("div", {className : "plate"}), {userAction : corners.none}),
elData(tag("span", {className : "edge lt"}), {userAction : corners.top + corners.left}),
elData(tag("span", {className : "edge rt"}), {userAction : corners.top + corners.right}),
elData(tag("span", {className : "edge lb"}), {userAction : corners.bottom + corners.left}),
elData(tag("span", {className : "edge rb"}), {userAction : corners.bottom + corners.right})
);
boxMap.set(box, update(boxDesc(box, width, height, x, y))); // update() sizes and positions elements
append(document.body, box);
if (addMouseEvents) { mouseEvents() }
}
};
})();
document.addEventListener('DOMContentLoaded', function() {
Squares.create(10, 100, 80, 80);
Squares.create(110, 100, 80, 80);
Squares.create(210, 100, 80, 80);
Squares.create(310, 100, 80, 80);
Squares.create(410, 100, 80, 80);
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
-moz-user-select: none;
}
.plate {
position: absolute;
width: 100%;
height: 100%;
background: rgb(240, 240, 240);
cursor: move;
}
.plate:hover {
background: #DEE;
}
.edge {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgb(211, 211, 211);
}
.edge:hover {
background: #CDD;
}
.edge.lt {
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.edge.rt {
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.edge.lb {
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.edge.rb {
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
const Squares = (() => {
/* Privates */
var addMouseEvents = true, focus;
const boxMap = new Map();
const corners = { // bit fields
none: 0, // 0b0000
top: 1, // 0b0001
bottom: 2, // 0b0010
left: 4, // 0b0100
right: 8, // 0b1000
};
const MIN_SIZE = 50;
const HANDLE_SIZE = 8; // adds 2 px
/* DOM helpers */
const tag = (name, props ={}) => Object.assign(document.createElement(name), props);
const elStyle = (el, style) => (Object.assign(el.style, style), el);
const elData = (el, data) => (Object.assign(el.dataset, data), el);
const append = (par, ...sibs) => {
for(const sib of sibs) { par.appendChild(sib) }
return par;
};
const boxDesc = (DOM, width, height, x, y) => ({ DOM, width, height, x, y });
function update(box) {
box.width = box.width < MIN_SIZE ? MIN_SIZE : box.width;
box.height = box.height < MIN_SIZE ? MIN_SIZE : box.height;
const right = innerWidth - (box.width + HANDLE_SIZE);
const bot = innerHeight - (box.height + HANDLE_SIZE);
box.x = box.x < HANDLE_SIZE ? HANDLE_SIZE : box.x > right ? right : box.x;
box.y = box.y < HANDLE_SIZE ? HANDLE_SIZE : box.y > bot ? bot : box.y;
elStyle(box.DOM, {
transform: `translate(${box.x}px, ${box.y}px)`,
width: box.width + "px",
height: box.height + "px",
});
return box;
}
function move(box, dx, dy) {
const bot = innerHeight - HANDLE_SIZE;
const right = innerWidth - HANDLE_SIZE;
if (box.action === corners.none) {
box.x += dx;
box.y += dy;
} else {
if ((box.action & corners.bottom) === corners.bottom) {
box.height = box.y + box.height + dy > bot ? bot - box.y : box.height + dy;
} else {
if (box.height - dy < MIN_SIZE) { dy = box.height - MIN_SIZE }
if (box.y + dy < HANDLE_SIZE) { dy = HANDLE_SIZE - box.y }
box.y += dy;
box.height -= dy;
}
if ((box.action & corners.right) === corners.right) {
box.width = box.x + box.width + dx > right ? right - box.x : box.width + dx;
} else {
if (box.width - dx < MIN_SIZE) { dx = box.width - MIN_SIZE }
if (box.x + dx < HANDLE_SIZE) { dx = HANDLE_SIZE - box.x }
box.x += dx;
box.width -= dx;
}
}
update(box);
}
function mouseEvent(e) {
if(e.type === "mousedown") {
if (e.target.dataset && e.target.dataset.userAction) {
focus = boxMap.get(e.target.parentNode);
focus.action = Number(e.target.dataset.userAction);
}
}else if(e.type === "mouseup") { focus = undefined }
else {
if (e.buttons === 0) { focus = undefined } // to stop sticky button in snippet
if (focus) { move(focus, e.movementX, e.movementY) }
}
}
function mouseEvents() {
document.addEventListener('mousedown', mouseEvent);
document.addEventListener('mouseup', mouseEvent);
document.addEventListener('mousemove', mouseEvent);
addMouseEvents = false;
}
return {
create(x, y, width, height) {
const box = append(
elStyle(tag("div"), { position: "absolute" }),
elData(tag("div", {className : "plate"}), {userAction : corners.none}),
elData(tag("span", {className : "edge lt"}), {userAction : corners.top + corners.left}),
elData(tag("span", {className : "edge rt"}), {userAction : corners.top + corners.right}),
elData(tag("span", {className : "edge lb"}), {userAction : corners.bottom + corners.left}),
elData(tag("span", {className : "edge rb"}), {userAction : corners.bottom + corners.right})
);
boxMap.set(box, update(boxDesc(box, width, height, x, y))); // update() sizes and positions elements
append(document.body, box);
if (addMouseEvents) { mouseEvents() }
}
};
})();
document.addEventListener('DOMContentLoaded', function() {
Squares.create(10, 100, 80, 80);
Squares.create(110, 100, 80, 80);
Squares.create(210, 100, 80, 80);
Squares.create(310, 100, 80, 80);
Squares.create(410, 100, 80, 80);
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
-moz-user-select: none;
}
.plate {
position: absolute;
width: 100%;
height: 100%;
background: rgb(240, 240, 240);
cursor: move;
}
.plate:hover {
background: #DEE;
}
.edge {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgb(211, 211, 211);
}
.edge:hover {
background: #CDD;
}
.edge.lt {
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.edge.rt {
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.edge.lb {
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.edge.rb {
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
const Squares = (() => {
/* Privates */
var addMouseEvents = true, focus;
const boxMap = new Map();
const corners = { // bit fields
none: 0, // 0b0000
top: 1, // 0b0001
bottom: 2, // 0b0010
left: 4, // 0b0100
right: 8, // 0b1000
};
const MIN_SIZE = 50;
const HANDLE_SIZE = 8; // adds 2 px
/* DOM helpers */
const tag = (name, props ={}) => Object.assign(document.createElement(name), props);
const elStyle = (el, style) => (Object.assign(el.style, style), el);
const elData = (el, data) => (Object.assign(el.dataset, data), el);
const append = (par, ...sibs) => {
for(const sib of sibs) { par.appendChild(sib) }
return par;
};
const boxDesc = (DOM, width, height, x, y) => ({ DOM, width, height, x, y });
function update(box) {
box.width = box.width < MIN_SIZE ? MIN_SIZE : box.width;
box.height = box.height < MIN_SIZE ? MIN_SIZE : box.height;
const right = innerWidth - (box.width + HANDLE_SIZE);
const bot = innerHeight - (box.height + HANDLE_SIZE);
box.x = box.x < HANDLE_SIZE ? HANDLE_SIZE : box.x > right ? right : box.x;
box.y = box.y < HANDLE_SIZE ? HANDLE_SIZE : box.y > bot ? bot : box.y;
elStyle(box.DOM, {
transform: `translate(${box.x}px, ${box.y}px)`,
width: box.width + "px",
height: box.height + "px",
});
return box;
}
function move(box, dx, dy) {
const bot = innerHeight - HANDLE_SIZE;
const right = innerWidth - HANDLE_SIZE;
if (box.action === corners.none) {
box.x += dx;
box.y += dy;
} else {
if ((box.action & corners.bottom) === corners.bottom) {
box.height = box.y + box.height + dy > bot ? bot - box.y : box.height + dy;
} else {
if (box.height - dy < MIN_SIZE) { dy = box.height - MIN_SIZE }
if (box.y + dy < HANDLE_SIZE) { dy = HANDLE_SIZE - box.y }
box.y += dy;
box.height -= dy;
}
if ((box.action & corners.right) === corners.right) {
box.width = box.x + box.width + dx > right ? right - box.x : box.width + dx;
} else {
if (box.width - dx < MIN_SIZE) { dx = box.width - MIN_SIZE }
if (box.x + dx < HANDLE_SIZE) { dx = HANDLE_SIZE - box.x }
box.x += dx;
box.width -= dx;
}
}
update(box);
}
function mouseEvent(e) {
if(e.type === "mousedown") {
if (e.target.dataset && e.target.dataset.userAction) {
focus = boxMap.get(e.target.parentNode);
focus.action = Number(e.target.dataset.userAction);
}
}else if(e.type === "mouseup") { focus = undefined }
else {
if (e.buttons === 0) { focus = undefined } // to stop sticky button in snippet
if (focus) { move(focus, e.movementX, e.movementY) }
}
}
function mouseEvents() {
document.addEventListener('mousedown', mouseEvent);
document.addEventListener('mouseup', mouseEvent);
document.addEventListener('mousemove', mouseEvent);
addMouseEvents = false;
}
return {
create(x, y, width, height) {
const box = append(
elStyle(tag("div"), { position: "absolute" }),
elData(tag("div", {className : "plate"}), {userAction : corners.none}),
elData(tag("span", {className : "edge lt"}), {userAction : corners.top + corners.left}),
elData(tag("span", {className : "edge rt"}), {userAction : corners.top + corners.right}),
elData(tag("span", {className : "edge lb"}), {userAction : corners.bottom + corners.left}),
elData(tag("span", {className : "edge rb"}), {userAction : corners.bottom + corners.right})
);
boxMap.set(box, update(boxDesc(box, width, height, x, y))); // update() sizes and positions elements
append(document.body, box);
if (addMouseEvents) { mouseEvents() }
}
};
})();
document.addEventListener('DOMContentLoaded', function() {
Squares.create(10, 100, 80, 80);
Squares.create(110, 100, 80, 80);
Squares.create(210, 100, 80, 80);
Squares.create(310, 100, 80, 80);
Squares.create(410, 100, 80, 80);
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
-moz-user-select: none;
}
.plate {
position: absolute;
width: 100%;
height: 100%;
background: rgb(240, 240, 240);
cursor: move;
}
.plate:hover {
background: #DEE;
}
.edge {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgb(211, 211, 211);
}
.edge:hover {
background: #CDD;
}
.edge.lt {
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.edge.rt {
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.edge.lb {
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.edge.rb {
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
edited 2 days ago
answered Mar 14 at 16:27
Blindman67Blindman67
8,5041521
8,5041521
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f215388%2fmove-and-resize-square-boxes-with-vanilla-js%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown