I have created a card tracker for a game.
It is based on a grid pattern of different divs that overlay a background image. Within these divs are smaller images that represent the cards and can be dragged from one div to the next.
I would like the divs to scale with the window, but they need to retain their aspect ratio so that they still overlay the correct parts of the background image as that scales.
I can get this working using "padding-top" for the divs; however, this then makes the smaller appear at the very bottom of that div and extends the div, and prevents them being dragged out and I can't see why it has this effect.
Many thanks in advance for any help!
Here's an example div without padding-top applied:
#div33 {
position: absolute;
top: 250px;
left: 542px;
width: 270px;
height: 350px;
margin: 2px;
padding: 2px;
background-color: rgba(255, 184, 53, 0.);
}
The draggable elements:
.draggable {
padding: 0px;
background-color: rgba(255, 255, 255, 0);
cursor: move;
padding: 1px;
}
.draggable.dragging {
opacity: .1;
}
and the script for dragging and dropping:
<script>
const draggables = document.querySelectorAll('.draggable')
const containers = document.querySelectorAll('.container')
draggables.forEach(draggable => {
draggable.addEventListener('dragstart', () => {
draggable.classList.add('dragging')
})
draggable.addEventListener('dragend', () => {
draggable.classList.remove('dragging')
})
})
containers.forEach(container => {
container.addEventListener('dragover', e => {
e.preventDefault()
const afterElement = getDragAfterElement(container, e.clientY)
const draggable = document.querySelector('.dragging')
if (afterElement == null) {
container.appendChild(draggable)
} else {
container.insertBefore(draggable, afterElement)
}
})
})
function getDragAfterElement(container, y) {
const draggableElements = [...container.querySelectorAll('.draggable:not(.dragging)')]
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect()
const offset = y - box.top - box.height / 2
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child }
} else {
return closest
}
}, { offset: Number.NEGATIVE_INFINITY }).element
}
</script>
I think I have answered my own question. I've just found out about the newer "aspect-ratio" property.
Related
But When i select the other picture it did not show in magnifier box, instead it show the default picture in magnifier. how i can fix that?. I want to change the image after selecting from below and magnifier should work on that image.
and magnifier position is very downside, can we also make the appropriate position
HTML
Css
.product-image {
height: 300px;
cursor: zoom-in;
}
.magnifier-container {
display: inline-block;
position: relative;
}
.magnifier {
display: none;
position: absolute;
top: 0;
left: 100%;
overflow: hidden;
height: 300px;
width: 300px;
border: 1px solid #ddd;
border-radius: 10px;
background-color: white;
}
.magnifier__img {
width: 1000px;
transform-origin: 150px 150px;
}
js
// most efficient way to add HTML, faster than innerHTML
const parseHTML = (htmlStr) => {
const range = document.createRange();
range.selectNode(document.body); // required in Safari
return range.createContextualFragment(htmlStr);
};
// pass this function any image element to add magnifying functionality
const makeImgMagnifiable = (img) => {
const magnifierFragment = parseHTML(`
<div class="magnifier-container">
<div class="magnifier">
<img class="magnifier__img" src="${img.src}"/>
</div>
</div>
`);
// This preserves the original element reference instead of cloning it.
img.parentElement.insertBefore(magnifierFragment, img);
const magnifierContainerEl = document.querySelector(".magnifier-container");
img.remove();
magnifierContainerEl.appendChild(img);
// query the DOM for the newly added elements
const magnifierEl = magnifierContainerEl.querySelector(".magnifier");
const magnifierImg = magnifierEl.querySelector(".magnifier__img");
// set up the transform object to be mutated as mouse events occur
const transform = {
translate: [0, 0],
scale: 1,
};
// shortcut function to set the transform css property
const setTransformStyle = (el, { translate, scale }) => {
const [xPercent, yRawPercent] = translate;
const yPercent = yRawPercent < 0 ? 0 : yRawPercent;
// make manual pixel adjustments to better center
// the magnified area over the cursor.
const [xOffset, yOffset] = [
`calc(-${xPercent}% + 250px)`,
`calc(-${yPercent}% + 70px)`,
];
el.style = `
transform: scale(${scale}) translate(${xOffset}, ${yOffset});
`;
};
// show magnified thumbnail on hover
img.addEventListener("mousemove", (event) => {
const [mouseX, mouseY] = [event.pageX + 40, event.pageY - 20];
const { top, left, bottom, right } = img.getBoundingClientRect();
transform.translate = [
((mouseX - left) / right) * 100,
((mouseY - top) / bottom) * 100,
];
magnifierEl.style = `
display: block;
top: ${mouseY}px;
left: ${mouseX}px;
`;
setTransformStyle(magnifierImg, transform);
});
// zoom in/out with mouse wheel
img.addEventListener("wheel", (event) => {
event.preventDefault();
const scrollingUp = event.deltaY < 0;
const { scale } = transform;
transform.scale =
scrollingUp && scale < 3
? scale + 0.1
: !scrollingUp && scale > 1
? scale - 0.1
: scale;
setTransformStyle(magnifierImg, transform);
});
// reset after mouse leaves
img.addEventListener("mouseleave", () => {
magnifierEl.style = "";
magnifierImg.style = "";
});
};
const img = document.querySelector(".product-image");
makeImgMagnifiable(img);
I am developing a custom element representing a window in an operating system. Like a window, it should be moveable/draggable. I can successfully move the window with this code:
moveVertically(movement) {
const boundingClientRect = this.getBoundingClientRect()
this.setTop(boundingClientRect.top + movement)
this.setBottom(boundingClientRect.bottom + movement)
}
moveHorizontally(movement) {
const boundingClientRect = this.getBoundingClientRect()
this.setLeft(boundingClientRect.left + movement)
this.setRight(boundingClientRect.right + movement)
}
The argument movement is mouse event variable movementY and movementX, respectively. The problem is that, if I move the window to quickly, it resizes as well.
How do I move the window without resizing it?
Edit: With this code:
moveVertically(movement) {
const boundingClientRect = this.getBoundingClientRect()
this.style.transform =
`translateY(${boundingClientRect.top + movement}px)`
}
moveHorizontally(movement) {
const boundingClientRect = this.getBoundingClientRect()
this.style.transform =
`translateX(${boundingClientRect.left + movement}px)`
}
the window kind of just moves up and down, when I try to move it. If I comment one of the methods out, it works perfectly, but I can't get both to work at the same time.
I haven't changed my resizing method yet, but that probably doesn't matter.
The reason why your second attempt doesn't work is because you are overwriting the CSS transform. You will need to persist the values on either axis: this can be done by using CSS custom properties for example.
The example below is an adapted example based on your code. The --translate-x and --translate-y are CSS custom properties that can be adjusted individually and they are simply being assigned to the CSS transform property using transform: translate(var(--translate-x, 0), var(--translate-y, 0)); (the fallback value is set to 0):
const stage = document.querySelector('#stage');
const el = document.querySelector('#el');
function moveVertically(movement) {
el.style.setProperty('--translate-y', `${movement}px`);
}
function moveHorizontally(movement) {
el.style.setProperty('--translate-x', `${movement}px`);
}
stage.addEventListener('mousemove', e => {
moveHorizontally(e.clientX);
moveVertically(e.clientY);
});
body {
margin: 0;
padding: 0;
}
#stage {
position: relative;
width: 100vw;
height: 100vh;
}
#el {
position: absolute;
width: 100px;
height: 100px;
background-color: steelblue;
transform: translate(var(--translate-x, 0), var(--translate-y, 0));
}
<div id="stage">
<div id="el"></div>
</div>
Alternatively you can also store the XY coordinates into a variable:
const stage = document.querySelector('#stage');
const el = document.querySelector('#el');
const elCoords = { x: 0, y: 0 };
function moveVertically(movement) {
elCoords.y = movement;
}
function moveHorizontally(movement) {
elCoords.x = movement;
}
stage.addEventListener('mousemove', e => {
moveHorizontally(e.clientX);
moveVertically(e.clientY);
el.style.transform = `translate(${elCoords.x}px, ${elCoords.y}px)`;
});
body {
margin: 0;
padding: 0;
}
#stage {
position: relative;
width: 100vw;
height: 100vh;
}
#el {
position: absolute;
width: 100px;
height: 100px;
background-color: steelblue;
}
<div id="stage">
<div id="el"></div>
</div>
My implementation,
http://kodhus.com/kodnest/land/PpNFTgp
I am curious, as I am not able for some reason to figure this out, how to get my JavaScript to make my slider behave more natural and smoother, if someone knows, how to, or can make this, feel free. I'd be happy to understand.
JavaScript:
const thumb = document.querySelector('.thumb');
const thumbIndicator = document.querySelector('.thumb .thumb-indicator');
const sliderContainer = document.querySelector('.slider-container');
const trackProgress = document.querySelector('.track-progress');
const sliderContainerStart = sliderContainer.offsetLeft;
const sliderContainerWidth = sliderContainer.offsetWidth;
var translate;
var dragging = false;
var percentage = 14;
document.addEventListener('mousedown', function(e) {
if (e.target.classList.contains('thumb-indicator')) {
dragging = true;
thumbIndicator.classList.add('focus');
}
});
document.addEventListener('mousemove', function(e) {
if (dragging) {
console.log('moving', e)
if (e.clientX < sliderContainerStart) {
translate = 0;
} else if (e.clientX > sliderContainerWidth + sliderContainerStart) {
translate = sliderContainerWidth;
} else {
translate = e.clientX - sliderContainer.offsetLeft;
}
thumb.style.transform = 'translate(-50%) translate(' + translate + 'px)';
trackProgress.style.transform = 'scaleX(' + translate / sliderContainerWidth + ')'
}
});
function setPercentage() {
thumb.style.transform = 'translate(-50%) translate(' + percentage/100 * sliderContainerWidth + 'px)';
trackProgress.style.transform = 'scaleX(' + percentage/100 + ')';
}
function init() {
setPercentage();
}
init();
document.addEventListener('mouseup', function(e) {
dragging = false;
thumbIndicator.classList.remove('focus');
});
EDIT: Is there a way to smoothly and naturally increment by one for every slow move?
Is it possible to make to behave as if, like when one clicks the progress bar so that it jumps there?
The kodhus site is very janky in my browser, so I can't tell if your code lacks responsiveness or whether it's the site itself. I feel that your code is a bit convoluted: translate and width / height are mixed unnecessarily; no need to use a dragging boolean when that information is always stored in the classlist. The following slider performs nicely, and has a few considerations I don't see in yours:
stopPropagation when clicking the .thumb element
drag stops if window loses focus
pointer-events: none; applied to every part of the slider but the .thumb element
let applySliderFeel = (slider, valueChangeCallback=()=>{}) => {
// Now `thumb`, `bar` and `slider` are the elements that concern us
let [ thumb, bar ] = [ '.thumb', '.bar' ].map(v => slider.querySelector(v));
let changed = amt => {
thumb.style.left = `${amt * 100}%`;
bar.style.width = `${amt * 100}%`;
valueChangeCallback(amt);
};
// Pressing down on `thumb` activates dragging
thumb.addEventListener('mousedown', evt => {
thumb.classList.add('active');
evt.preventDefault();
evt.stopPropagation();
});
// Releasing the mouse button (anywhere) deactivates dragging
document.addEventListener('mouseup', evt => thumb.classList.remove('active'));
// If the window loses focus dragging also stops - this can be a very
// nice quality of life improvement!
window.addEventListener('blur', evt => thumb.classList.remove('active'));
// Now we have to act when the mouse moves...
document.addEventListener('mousemove', evt => {
// If the drag isn't active do nothing!
if (!thumb.classList.contains('active')) return;
// Compute `xRelSlider`, which is the mouse position relative to the
// left side of the slider bar. Note that *client*X is compatible with
// getBounding*Client*Rect, and using these two values we can quickly
// get the relative x position.
let { width, left } = slider.getBoundingClientRect();
// Consider mouse x, subtract left offset of slider, and subtract half
// the width of the thumb (so drags position the center of the thumb,
// not its left side):
let xRelSlider = evt.clientX - left - (thumb.getBoundingClientRect().width >> 1);
// Clamp `xRelSlider` between 0 and the slider's width
if (xRelSlider < 0) xRelSlider = 0;
if (xRelSlider > width) xRelSlider = width;
// Apply styling (using percents is more robust!)
changed(xRelSlider / width);
evt.preventDefault();
evt.stopPropagation();
});
slider.addEventListener('mousedown', evt => {
let { width, left } = slider.getBoundingClientRect();
// Clicking the slider also activates a drag
thumb.classList.add('active');
// Consider mouse x, subtract left offset of slider, and subtract half
// the width of the thumb (so drags position the center of the thumb,
// not its left side):
let xRelSlider = evt.clientX - left - (thumb.getBoundingClientRect().width >> 1);
// Apply styling (using percents is more robust!)
changed(xRelSlider / width);
evt.preventDefault();
evt.stopPropagation();
});
changed(0);
};
let valElem = document.querySelector('.value');
applySliderFeel(document.querySelector('.slider'), amt => valElem.innerHTML = amt.toFixed(3));
.slider {
position: absolute;
width: 80%; height: 4px; background-color: rgba(0, 0, 0, 0.3);
left: 10%; top: 50%; margin-top: -2px;
}
.slider > .bar {
position: absolute;
left: 0; top: 0; width: 0; height: 100%;
background-color: #000;
pointer-events: none;
}
.slider > .thumb {
position: absolute;
width: 20px; height: 20px; background-color: #000; border-radius: 100%;
left: 0; top: 50%; margin-top: -10px;
}
.slider > .thumb.active {
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.5);
}
<div class="slider">
<div class="bar"></div>
<div class="thumb"></div>
</div>
<div class="value"></div>
I have side tabs that are overlaying. I want to be able to click one and do something with it, but I am having trouble getting my event handlers to register correct div.
<div class="tabs">
<div class="blueTab">
<div class="blueTab__container">
</div>
</div>
<div class="brownTab">
<div class="brownTab__container">
</div>
</div>
</div>
.tabs {
position: relative;
margin-left: 853px;
}
.brownTab {
position: absolute;
.brownTab__container {
background-image: url(https://i.imgur.com/Fd49HkH.png) !important;
background-repeat: no-repeat !important;
width: 400px;
height: 853px;
}
}
.blueTab {
position: absolute;
.blueTab__container {
background-image: url(https://i.imgur.com/F5qg6xo.png) !important;
background-repeat: no-repeat !important;
width: 400px;
height: 873px;
}
}
let brownTabElement = document.getElementsByClassName('brownTab')[0]
let blueTabElement = document.getElementsByClassName('blueTab')[0]
brownTabElement.addEventListener('click', brownTabHandler, false)
blueTabElement.addEventListener('click', blueTabHandler, false)
function brownTabHandler() {
alert('brownTab')
}
function blueTabHandler() {
alert('blueTab')
}
When ever I click both the blue or brown tab, only the event handler for the browntab gets called. How can I fix this?
https://jsfiddle.net/Le80rvL0/3/
Your problem is that, even though you can see both tabs (because their backgrounds are partially transparent), the .brownTab__container element is overlapping the .blueTab__container, as they are in the exact same position, so you always click on that one. You can see that using the developer tools' inspector.
One solution to your problem would be to actually create different, non-overlapping elements that look like tabs. Maybe something like this or like this.
Another solution could be to use a single HTML element with a single background image of all the tabs, get the color of the clicked pixel and use it to distinguish the clicked tab. Something like this:
const message = document.getElementById('message');
// Get the tabs element:
const tabs = document.getElementById('tabs');
// Get its computed styles:
const style = window.getComputedStyle(tabs);
// Extract the background-image's url:
const backgroundImage = style.getPropertyValue('background-image');
const url = backgroundImage.substring(5, backgroundImage.length - 2);
// Create an image with that url:
const img = document.createElement('IMG');
img.src = url;
// Create a canvas with the original size of the image:
const canvas = document.createElement('CANVAS');
const width = canvas.width = 1;
const height = canvas.height = 123;
// Draw that image on it:
canvas.getContext('2d').drawImage(img, 0, 0, width, height);
// Listen for click events:
document.getElementById('tabs').addEventListener('click', (e) => {
// Get clicked pixel's RGBA value, taking into account the image on the tabs element is
// repeated, while the one on the canvas it's not:
const rgba = canvas.getContext('2d').getImageData(e.offsetX % width, e.offsetY % height, 1, 1).data;
// Set color thresholds for each tab (as in your images the colors are not uniform as
// they are here:
if (rgba[2] > 200) {
message.innerText = 'You clicked the blue tab.';
} else if (rgba[2] < 150 && rgba[1] > 50) {
message.innerText = 'You clicked the brown tab.';
} else if (rgba[1] > 200) {
message.innerText = 'You clicked the gold tab.';
} else if (rgba[1] < 50) {
message.innerText = 'You clicked the purple tab.';
} else {
message.innerText = 'Unknown tab clicked.';
}
});
body {
position: relative;
margin: 0;
height: 800px;
}
#message {
position: fixed;
top: 0;
left: 0;
right: 90px;
padding: 16px;
font-size: 32px;
}
#tabs {
position: absolute;
width: 90px;
height: 100%;
top: 0;
right: 0;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAB7CAYAAAC1rOouAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAqSURBVChTYzAOPfN/kBJnGtIGK/H+yYbBSpzRBjkSRNzUTkfmDhyR9h8AMPtPIMUTD3YAAAAASUVORK5CYII=);
}
<div id="message"></div>
<div id="tabs"></div>
Keep in mind that with the first approach it will be way easier to add other effects and features like hover/active/focus styles, add/remove tabs, move tabs, drag tabs...
I want to make a very simple function in my jQuery script. When the finger/cursor touches/clicks on the screen, I want the pages to slide horizontally following the movements of the finger/cursor. I know there is a lot of plugins created by so many people, but I really don't need everybody else's solutions. The image is a visual view of how my HTML looks like. it is really simple.
The jQuery sciprt is obviously not correct, but I hope it would give you an idea about the simple function I need. I don't extra classes or fade-functions or anything.
$(document).live('touchmove' or 'mousemove', function() {
$('div[class=page_*], div[class=page_^]').[follow movements horizontally, and auto align to nearest edge when let go.];
});
Also I want to be able to do the same with one big div, so probably the width-variable of the element moving should be equal to $(window).width();. Actually I think that would be the best idea. I can always put more content inside the big div and make it larger, so keep it with that. It should be more simple to do and to focus on one element only.
So, here is my solution. I've made some changes so that now you can have more than 3 pages.
Also, I've defined a variable named threshold set to the half of a page. If you want to have a threshold bigger or smaller than the hakf of the page you will have to make some more changes.
HTML CODE:
<div class="container">
<div class="wrap">
<div class="page page1"></div>
<div class="page page2"></div>
<div class="page page3"></div>
<div class="page page4"></div>
</div>
</div>
CSS CODE:
.container, .page, .wrap {
width: 300px;
height: 400px;
}
.container {
background: #efefef;
box-shadow: 0px 0px 10px black;
overflow: hidden;
position: relative;
margin: 5px auto;
}
.wrap {
width: 1200px;
position: absolute;
top: 0;
left: 0;
}
.page {
float: left;
display: block;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-o-user-select: none;
}
.page1 {
background: yellow;
}
.page2 {
background: green;
}
.page3 {
background: blue;
}
.page4 {
background: red;
}
As for the CSS code keep in mind that if you want to change the page size you will also have to change the container and wrap size.
JS CODE:
var mouseDown = false, right;
var xi, xf, leftX = 0;
var nPages = $(".page").size();
var pageSize = $(".page").width();
var threshold = pageSize/2;
var currentPage = 0;
$(".container").on("mousedown", function (e) {
mouseDown = true;
xi = e.pageX;
});
$(".container").on("mouseup", function (e) {
if (mouseDown) {
mouseDown = false;
xf = e.pageX;
leftX = parseInt($(".wrap").css("left").split("px")[0]);
if ((e.pageX - xi) < -threshold || (e.pageX - xi) > threshold) {
setFocusedPage();
} else {
restore();
}
}
});
$(".container").on("mouseleave", function (e) {
if (mouseDown) {
mouseDown = false;
xf = e.pageX;
leftX = parseInt($(".wrap").css("left").split("px")[0]);
if ((e.pageX - xi) < -threshold || (e.pageX - xi) > threshold) {
setFocusedPage();
} else {
restore();
}
}
});
$(".container").on("mousemove", function (e) {
if (mouseDown) {
$(".wrap").css({
"left": (leftX + (e.pageX - xi))
});
right = ((e.pageX - xi) < 0) ? true : false;
}
});
function restore() {
$(".wrap").stop().animate({
"left": -(currentPage * pageSize)
}, 200, function () {
leftX = parseInt($(".wrap").css("left").split("px")[0]);
});
}
function setFocusedPage() {
if (leftX >= (-threshold)) { // First Page
currentPage = 0;
} else if (leftX < (-threshold) && leftX >= (-(nPages + 1) * threshold)) { // Second to N-1 Page
(right) ? currentPage++ : currentPage--;
} else if (leftX < -((nPages + 1) * threshold)) { // Third Page
currentPage = nPages - 1;
}
$(".wrap").stop().animate({
"left": -(currentPage * pageSize)
}, 200, function () {
leftX = parseInt($(".wrap").css("left").split("px")[0]);
});
}
Remember here that if you want a different threshold you will have to make some changes especially in the setFocusedPage() function.
Here is my last DEMO