JQuery Draggable - Prevent grid objects from going in the same position - javascript

I'm using JQuery Draggable to move items round a grid. Objects snap to a 32x32 grid area. I want to be able to cancel a grid snap if an object is in the same position.
The drag cannot be cancelled, it must just be prevented from entering the square. After it is prevented and moved back to the previous position, if the user continues to drag into a new unoccupied grid position, it must snap to that one.
I've created a demo which serves the purpose explained above however the image glitches when it tries to enter the new position but is then cancelled back to the old position.
https://jsfiddle.net/dtx7my4e/1/
Here's the code in that fiddle:
HTML:
<div class="drop-target">
<div class="drag-item" object-id="0"></div>
<div class="drag-item" style="left: 32px;" object-id="1"></div>
</div>
Javascript:
var objects = [
[0, 0],
[1, 1]
];
$(function() {
$(".drag-item").draggable({
grid: [ 32, 32 ],
containment: '.drop-target',
drag: function (event, obj){
let objectId = $(this).attr('object-id');
var objectPositionX = $(this).position().left / 32;
var objectPositionY = $(this).position().top / 32;
var previousPositionX = Math.floor(objects[objectId][0]);
var previousPositionY = Math.floor(objects[objectId][1]);
if (objectPositionX != previousPositionX || objectPositionY != previousPositionY) {
if(!isObjectInPosition(objects, [objectPositionX, objectPositionY])) {
objects[objectId] = [objectPositionX, objectPositionY];
} else {
obj.position.left = previousPositionX * 32;
obj.position.top = previousPositionY * 32;
}
}
}
});
});
function isObjectInPosition(arrayToSearch, positionToFind)
{
for (let i = 0; i < arrayToSearch.length; i++) {
if (arrayToSearch[i][0] == positionToFind[0]
&& arrayToSearch[i][1] == positionToFind[1]) {
return true;
}
}
return false;
}
CSS:
.drag-item {
background-image: url("http://i.imgur.com/lBIWrWw.png");
background-size: 32px auto;
width: 32px;
height: 32px;
cursor: move;
}
.drop-target {
background: whitesmoke url("http://i.imgur.com/uUvTRLx.png") repeat scroll 0 0 / 32px 32px;
border: 1px dashed orange;
height: 736px;
left: 0;
position: absolute;
top: 0;
width: 736px;
}
Thank you, any help is greatly appreciated.
Toby.

If you're willing to modify draggable itself, I think it would make the logic easier to apply. Once the drag event is triggered you can do lots of things, but you have much more control if you modify the _generatePosition method of draggable. It may look more complicated at first but for this kind of behavior, it's sometimes easier to work.
Basically, you can run your isInPosition function after the check for grid and containment has been applied. Normally next step is to set the new position, but if your isInPosition returns true, you prevent dragging. Something like this:
'use strict'
// This is the function generating the position by calculating
// mouse position, different offsets and option.
$.ui.draggable.prototype._generatePosition = function(event, constrainPosition) {
var containment, co, top, left,
o = this.options,
scrollIsRootNode = this._isRootNode(this.scrollParent[0]),
pageX = event.pageX,
pageY = event.pageY;
// Cache the scroll
if (!scrollIsRootNode || !this.offset.scroll) {
this.offset.scroll = {
top: this.scrollParent.scrollTop(),
left: this.scrollParent.scrollLeft()
};
}
/*
* - Position constraining -
* Constrain the position to a mix of grid, containment.
*/
// If we are not dragging yet, we won't check for options
if (constrainPosition) {
if (this.containment) {
if (this.relativeContainer) {
co = this.relativeContainer.offset();
containment = [
this.containment[0] + co.left,
this.containment[1] + co.top,
this.containment[2] + co.left,
this.containment[3] + co.top
];
} else {
containment = this.containment;
}
if (event.pageX - this.offset.click.left < containment[0]) {
pageX = containment[0] + this.offset.click.left;
}
if (event.pageY - this.offset.click.top < containment[1]) {
pageY = containment[1] + this.offset.click.top;
}
if (event.pageX - this.offset.click.left > containment[2]) {
pageX = containment[2] + this.offset.click.left;
}
if (event.pageY - this.offset.click.top > containment[3]) {
pageY = containment[3] + this.offset.click.top;
}
}
if (o.grid) {
//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
}
if (o.axis === "y") {
pageX = this.originalPageX;
}
if (o.axis === "x") {
pageY = this.originalPageY;
}
}
// This is the only part added to the original function.
// You have access to the updated position after it's been
// updated through containment and grid, but before the
// element is modified.
// If there's an object in position, you prevent dragging.
if (isObjectInPosition(objects, [pageX - this.offset.click.left - this.offset.parent.left, pageY - this.offset.click.top - this.offset.parent.top])) {
return false;
}
return {
top: (
pageY - // The absolute mouse position
this.offset.click.top - // Click offset (relative to the element)
this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent
this.offset.parent.top + // The offsetParent's offset without borders (offset + border)
(this.cssPosition === "fixed" ? -this.offset.scroll.top : (scrollIsRootNode ? 0 : this.offset.scroll.top))
),
left: (
pageX - // The absolute mouse position
this.offset.click.left - // Click offset (relative to the element)
this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent
this.offset.parent.left + // The offsetParent's offset without borders (offset + border)
(this.cssPosition === "fixed" ? -this.offset.scroll.left : (scrollIsRootNode ? 0 : this.offset.scroll.left))
)
};
}
var objects = [
[0, 0],
[1, 1]
];
$(function() {
$(".drag-item").draggable({
grid: [32, 32],
containment: '.drop-target',
// on start you remove coordinate of dragged item
// else it'll check its own coordinates
start: function(event, obj) {
var objectId = $(this).attr('object-id');
objects[objectId] = [null, null];
},
// on stop you update your array
stop: function(event, obj) {
var objectId = $(this).attr('object-id');
var objectPositionX = $(this).position().left / 32;
var objectPositionY = $(this).position().top / 32;
objects[objectId] = [objectPositionX, objectPositionY];
}
});
});
function isObjectInPosition(arrayToSearch, positionToFind) {
for (let i = 0; i < arrayToSearch.length; i++) {
if (arrayToSearch[i][0] === (positionToFind[0] / 32) && arrayToSearch[i][1] === (positionToFind[1] / 32)) {
return true;
}
}
return false;
}
https://jsfiddle.net/bfc4tsrh/1/

Related

Image Carousel Codepen Images not loading

I have been trying to implement this codepen [https://codepen.io/LokeshNagarajan/pen/apRmZe?editors=1111] on my website but like in the codepen the images do not load even if I assign an https src and i believe the issue stems from the javascript but not sure exactly what to do to fix it.
(function ($) {
'use strict';
$.fn.waterwheelCarousel = function (startingOptions) {
// Adds support for intializing multiple carousels from the same selector group
if (this.length > 1) {
this.each(function () {
$(this).waterwheelCarousel(startingOptions);
});
return this; // allow chaining
}
var carousel = this;
var options = {};
var data = {};
function initializeCarouselData() {
data = {
itemsContainer: $(carousel),
totalItems: $(carousel).children().length,
containerWidth: $(carousel).width(),
containerHeight: $(carousel).height(),
currentCenterItem: null,
previousCenterItem: null,
items: [],
calculations: [],
carouselRotationsLeft: 0,
currentlyMoving: false,
itemsAnimating: 0,
currentSpeed: options.speed,
intervalTimer: null,
currentDirection: 'forward',
leftItemsCount: 0,
rightItemsCount: 0,
performingSetup: true
};
data.itemsContainer.children().removeClass(options.activeClassName);
}
/**
* This function will set the autoplay for the carousel to
* automatically rotate it given the time in the options
* Can clear the autoplay by passing in true
*/
function autoPlay(stop) {
// clear timer
clearTimeout(data.autoPlayTimer);
// as long as no stop command, and autoplay isn't zeroed...
if (!stop && options.autoPlay !== 0) {
// set timer...
data.autoPlayTimer = setTimeout(function () {
// to move the carousl in either direction...
if (options.autoPlay > 0) {
moveOnce('forward');
} else {
moveOnce('backward');
}
}, Math.abs(options.autoPlay));
}
}
/**
* This function will preload all the images in the carousel before
* calling the passed in callback function. This is only used so we can
* properly determine the width and height of the items. This is not needed
* if a user instead manually specifies that information.
*/
function preload(callback) {
var $imageElements = data.itemsContainer.find('img'),
totalImages = $imageElements.length,
loadedImages = 0;
if (options.preloadImages === false || $imageElements.length === 0) {
callback();
return;
}
$imageElements.each(function () {
$(this).bind('load', function () {
// Add to number of images loaded and see if they are all done yet
loadedImages += 1;
if (loadedImages === totalImages) {
// All done, perform callback
callback();
return;
}
});
// May need to manually reset the src to get the load event to fire
// http://stackoverflow.com/questions/7137737/ie9-problems-with-jquery-load-event-not-firing
$(this).attr('src', $(this).attr('src'));
// If browser has cached the images, it may not call trigger a load. Detect this and do it ourselves
if (this.complete) {
$(this).trigger('load');
}
});
}
/**
* Makes a record of the original width and height of all the items in the carousel.
* If we re-intialize the carousel, these values can be used to re-establish their
* original dimensions.
*/
function setOriginalItemDimensions() {
data.itemsContainer.children().each(function () {
if ($(this).data('original_width') == undefined || options.forcedImageWidth > 0) {
$(this).data('original_width', $(this).width());
}
if ($(this).data('original_height') == undefined || options.forcedImageHeight > 0) {
$(this).data('original_height', $(this).height());
}
});
}
/**
* Users can pass in a specific width and height that should be applied to every image.
* While this option can be used in conjunction with the image preloader, the intended
* use case is for when the preloader is turned off and the images don't have defined
* dimensions in CSS. The carousel needs dimensions one way or another to work properly.
*/
function forceImageDimensionsIfEnabled() {
if (options.forcedImageWidth && options.forcedImageHeight) {
data.itemsContainer.children().each(function () {
$(this).width(options.forcedImageWidth);
$(this).height(options.forcedImageHeight);
});
}
}
/**
* For each "visible" item slot (# of flanking items plus the middle),
* we pre-calculate all of the properties that the item should possess while
* occupying that slot. This saves us some time during the actual animation.
*/
function preCalculatePositionProperties() {
// The 0 index is the center item in the carousel
var $firstItem = data.itemsContainer.children().eq(0);
data.calculations[0] = {
distance: 0,
offset: 0,
opacity: 1
}
// Then, for each number of flanking items (plus one more, see below), we
// perform the calcations based on our user options
var horizonOffset = options.horizonOffset;
var separation = options.separation;
for (var i = 1; i <= options.flankingItems + 2; i++) {
if (i > 1) {
horizonOffset *= options.horizonOffsetMultiplier;
separation *= options.separationMultiplier;
}
data.calculations[i] = {
distance: data.calculations[i - 1].distance + separation,
offset: data.calculations[i - 1].offset + horizonOffset,
opacity: data.calculations[i - 1].opacity * options.opacityMultiplier
}
}
// We performed 1 extra set of calculations above so that the items that
// are moving out of sight (based on # of flanking items) gracefully animate there
// However, we need them to animate to hidden, so we set the opacity to 0 for
// that last item
if (options.edgeFadeEnabled) {
data.calculations[options.flankingItems + 1].opacity = 0;
} else {
data.calculations[options.flankingItems + 1] = {
distance: 0,
offset: 0,
opacity: 0
}
}
}
/**
* Here we prep the carousel and its items, like setting default CSS
* attributes. All items start in the middle position by default
* and will "fan out" from there during the first animation
*/
function setupCarousel() {
// Fill in a data array with jQuery objects of all the images
data.items = data.itemsContainer.children();
for (var i = 0; i < data.totalItems; i++) {
data.items[i] = $(data.items[i]);
}
// May need to set the horizon if it was set to auto
if (options.horizon === 0) {
if (options.orientation === 'horizontal') {
options.horizon = data.containerHeight / 2;
} else {
options.horizon = data.containerWidth / 2;
}
}
// Default all the items to the center position
data.itemsContainer
.css('position', 'relative')
.children()
.each(function () {
// Figure out where the top and left positions for center should be
var centerPosLeft, centerPosTop;
if (options.orientation === 'horizontal') {
centerPosLeft = (data.containerWidth / 2) - ($(this).data('original_width') / 2);
centerPosTop = options.horizon - ($(this).data('original_height') / 2);
} else {
centerPosLeft = options.horizon - ($(this).data('original_width') / 2);
centerPosTop = (data.containerHeight / 2) - ($(this).data('original_height') / 2);
}
$(this)
// Apply positioning and layering to the images
.css({
'left': centerPosLeft,
'top': centerPosTop,
'visibility': 'visible',
'position': 'absolute',
'z-index': 0,
'opacity': 0
})
// Give each image a data object so it remembers specific data about
// it's original form
.data({
top: centerPosTop,
left: centerPosLeft,
oldPosition: 0,
currentPosition: 0,
depth: 0,
opacity: 0
})
// The image has been setup... Now we can show it
.show();
});
}
/**
* All the items to the left and right of the center item need to be
* animated to their starting positions. This function will
* figure out what items go where and will animate them there
*/
function setupStarterRotation() {
options.startingItem = (options.startingItem === 0) ? Math.round(data.totalItems / 2) : options.startingItem;
data.rightItemsCount = Math.ceil((data.totalItems - 1) / 2);
data.leftItemsCount = Math.floor((data.totalItems - 1) / 2);
// We are in effect rotating the carousel, so we need to set that
data.carouselRotationsLeft = 1;
// Center item
moveItem(data.items[options.startingItem - 1], 0);
data.items[options.startingItem - 1].css('opacity', 1);
// All the items to the right of center
var itemIndex = options.startingItem - 1;
for (var pos = 1; pos <= data.rightItemsCount; pos++) {
(itemIndex < data.totalItems - 1) ? itemIndex += 1 : itemIndex = 0;
data.items[itemIndex].css('opacity', 1);
moveItem(data.items[itemIndex], pos);
}
// All items to left of center
var itemIndex = options.startingItem - 1;
for (var pos = -1; pos >= data.leftItemsCount * -1; pos--) {
(itemIndex > 0) ? itemIndex -= 1 : itemIndex = data.totalItems - 1;
data.items[itemIndex].css('opacity', 1);
moveItem(data.items[itemIndex], pos);
}
}
/**
* Given the item and position, this function will calculate the new data
* for the item. One the calculations are done, it will store that data in
* the items data object
*/
function performCalculations($item, newPosition) {
var newDistanceFromCenter = Math.abs(newPosition);
// Distance to the center
if (newDistanceFromCenter < options.flankingItems + 1) {
var calculations = data.calculations[newDistanceFromCenter];
} else {
var calculations = data.calculations[options.flankingItems + 1];
}
var distanceFactor = Math.pow(options.sizeMultiplier, newDistanceFromCenter)
var newWidth = distanceFactor * $item.data('original_width');
var newHeight = distanceFactor * $item.data('original_height');
var widthDifference = Math.abs($item.width() - newWidth);
var heightDifference = Math.abs($item.height() - newHeight);
var newOffset = calculations.offset
var newDistance = calculations.distance;
if (newPosition < 0) {
newDistance *= -1;
}
if (options.orientation == 'horizontal') {
var center = data.containerWidth / 2;
var newLeft = center + newDistance - (newWidth / 2);
var newTop = options.horizon - newOffset - (newHeight / 2);
} else {
var center = data.containerHeight / 2;
var newLeft = options.horizon - newOffset - (newWidth / 2);
var newTop = center + newDistance - (newHeight / 2);
}
newTop -= heightDifference / 2;
newLeft -= widthDifference / 2;
var newOpacity;
if (newPosition === 0) {
newOpacity = 1;
} else {
newOpacity = calculations.opacity;
}
// Depth will be reverse distance from center
var newDepth = options.flankingItems + 2 - newDistanceFromCenter;
$item.data('width', newWidth);
$item.data('height', newHeight);
$item.data('top', newTop);
$item.data('left', newLeft);
$item.data('oldPosition', $item.data('currentPosition'));
$item.data('depth', newDepth);
$item.data('opacity', newOpacity);
$item.data('distanceFactor', distanceFactor);
}
function moveItem($item, newPosition) {
// Only want to physically move the item if it is within the boundaries
// or in the first position just outside either boundary
if (Math.abs(newPosition) <= options.flankingItems + 1) {
performCalculations($item, newPosition);
data.itemsAnimating++;
$item
.css('z-index', $item.data().depth)
// Animate the items to their new position values
.animate({
'left': $item.data().left,
'top': $item.data().top,
'opacity': $item.data().opacity
}, data.currentSpeed, options.animationEasing, function () {
// Animation for the item has completed, call method
itemAnimationComplete($item, newPosition);
});
TweenMax.to($item, data.currentSpeed / 1000, {
scaleX: $item.data('distanceFactor'),
scaleY: $item.data('distanceFactor'),
ease: options.animationEasing
});
} else {
$item.data('currentPosition', newPosition)
// Move the item to the 'hidden' position if hasn't been moved yet
// This is for the intitial setup
if ($item.data('oldPosition') === 0) {
$item
.css({
'left': $item.data().left,
'top': $item.data().top,
'opacity': $item.data().opacity,
'z-index': $item.data().depth
});
TweenMax.to($item, 0, {
scaleX: $item.data('distanceFactor'),
scaleY: $item.data('distanceFactor')
});
}
}
}
/**
* This function is called once an item has finished animating to its
* given position. Several different statements are executed here, such as
* dealing with the animation queue
*/
function itemAnimationComplete($item, newPosition) {
data.itemsAnimating--;
$item.data('currentPosition', newPosition);
// Keep track of what items came and left the center position,
// so we can fire callbacks when all the rotations are completed
if (newPosition === 0) {
data.currentCenterItem = $item;
}
// all items have finished their rotation, lets clean up
if (data.itemsAnimating === 0) {
data.carouselRotationsLeft -= 1;
data.currentlyMoving = false;
// If there are still rotations left in the queue, rotate the carousel again
// we pass in zero because we don't want to add any additional rotations
if (data.carouselRotationsLeft > 0) {
rotateCarousel(0);
// Otherwise there are no more rotations and...
} else {
// Reset the speed of the carousel to original
data.currentSpeed = options.speed;
data.currentCenterItem.addClass(options.activeClassName);
if (data.performingSetup === false) {
options.movedToCenter(data.currentCenterItem);
options.movedFromCenter(data.previousCenterItem);
}
data.performingSetup = false;
// reset & initate the autoPlay
autoPlay();
}
}
}
/**
* Function called to rotate the carousel the given number of rotations
* in the given direciton. Will check to make sure the carousel should
* be able to move, and then adjust speed and move items
*/
function rotateCarousel(rotations) {
// Check to see that a rotation is allowed
if (data.currentlyMoving === false) {
// Remove active class from the center item while we rotate
data.currentCenterItem.removeClass(options.activeClassName);
data.currentlyMoving = true;
data.itemsAnimating = 0;
data.carouselRotationsLeft += rotations;
if (options.quickerForFurther === true) {
// Figure out how fast the carousel should rotate
if (rotations > 1) {
data.currentSpeed = options.speed / rotations;
}
// Assure the speed is above the minimum to avoid weird results
data.currentSpeed = (data.currentSpeed < 100) ? 100 : data.currentSpeed;
}
// Iterate thru each item and move it
for (var i = 0; i < data.totalItems; i++) {
var $item = $(data.items[i]);
var currentPosition = $item.data('currentPosition');
var newPosition;
if (data.currentDirection == 'forward') {
newPosition = currentPosition - 1;
} else {
newPosition = currentPosition + 1;
}
// We keep both sides as even as possible to allow circular rotation to work.
// We will "wrap" the item arround to the other side by negating its current position
var flankingAllowance = (newPosition > 0) ? data.rightItemsCount : data.leftItemsCount;
if (Math.abs(newPosition) > flankingAllowance) {
newPosition = currentPosition * -1;
// If there's an uneven number of "flanking" items, we need to compenstate for that
// when we have an item switch sides. The right side will always have 1 more in that case
if (data.totalItems % 2 == 0) {
newPosition += 1;
}
}
moveItem($item, newPosition);
}
}
}
/**
* The event handler when an image within the carousel is clicked
* This function will rotate the carousel the correct number of rotations
* to get the clicked item to the center, or will fire the custom event
* the user passed in if the center item is clicked
*/
$(this).children().bind("click", function () {
var itemPosition = $(this).data().currentPosition;
if (options.imageNav == false) {
return;
}
// Don't allow hidden items to be clicked
if (Math.abs(itemPosition) >= options.flankingItems + 1) {
return;
}
// Do nothing if the carousel is already moving
if (data.currentlyMoving) {
return;
}
data.previousCenterItem = data.currentCenterItem;
// Remove autoplay
autoPlay(true);
options.autoPlay = 0;
var rotations = Math.abs(itemPosition);
if (itemPosition == 0) {
options.clickedCenter($(this));
} else {
// Fire the 'moving' callbacks
options.movingFromCenter(data.currentCenterItem);
options.movingToCenter($(this));
if (itemPosition < 0) {
data.currentDirection = 'backward';
rotateCarousel(rotations);
} else if (itemPosition > 0) {
data.currentDirection = 'forward';
rotateCarousel(rotations);
}
}
});
function nextItemFromCenter() {
var $next = data.currentCenterItem.next();
if ($next.length <= 0) {
$next = data.currentCenterItem.parent().children().first();
}
return $next;
}
function prevItemFromCenter() {
var $prev = data.currentCenterItem.prev();
if ($prev.length <= 0) {
$prev = data.currentCenterItem.parent().children().last();
}
return $prev;
}
/**
* Intiate a move of the carousel in either direction. Takes care of firing
* the 'moving' callbacks
*/
function moveOnce(direction) {
if (data.currentlyMoving === false) {
data.previousCenterItem = data.currentCenterItem;
options.movingFromCenter(data.currentCenterItem);
if (direction == 'backward') {
options.movingToCenter(prevItemFromCenter());
data.currentDirection = 'backward';
} else if (direction == 'forward') {
options.movingToCenter(nextItemFromCenter());
data.currentDirection = 'forward';
}
}
rotateCarousel(1);
}
/**
* Navigation with arrow keys
*/
$(document).keydown(function (e) {
if (options.keyboardNav) {
// arrow left or up
if ((e.which === 37 && options.orientation == 'horizontal') || (e.which === 38 && options.orientation == 'vertical')) {
autoPlay(true);
options.autoPlay = 0;
moveOnce('backward');
// arrow right or down
} else if ((e.which === 39 && options.orientation == 'horizontal') || (e.which === 40 && options.orientation == 'vertical')) {
autoPlay(true);
options.autoPlay = 0;
moveOnce('forward');
}
// should we override the normal functionality for the arrow keys?
if (options.keyboardNavOverride && (
(options.orientation == 'horizontal' && (e.which === 37 || e.which === 39)) ||
(options.orientation == 'vertical' && (e.which === 38 || e.which === 40))
)) {
e.preventDefault();
return false;
}
}
});
/**
* Public API methods
*/
this.reload = function (newOptions) {
if (typeof newOptions === "object") {
var combineDefaultWith = newOptions;
} else {
var combineDefaultWith = {};
}
options = $.extend({}, $.fn.waterwheelCarousel.defaults, newOptions);
initializeCarouselData();
data.itemsContainer.children().hide();
forceImageDimensionsIfEnabled();
preload(function () {
setOriginalItemDimensions();
preCalculatePositionProperties();
setupCarousel();
setupStarterRotation();
});
}
this.next = function () {
autoPlay(true);
options.autoPlay = 0;
moveOnce('forward');
}
this.prev = function () {
autoPlay(true);
options.autoPlay = 0;
moveOnce('backward');
}
this.reload(startingOptions);
return this;
};
$.fn.waterwheelCarousel.defaults = {
// number tweeks to change apperance
startingItem: 1, // item to place in the center of the carousel. Set to 0 for auto
separation: 175, // distance between items in carousel
separationMultiplier: 0.6, // multipled by separation distance to increase/decrease distance for each additional item
horizonOffset: 0, // offset each item from the "horizon" by this amount (causes arching)
horizonOffsetMultiplier: 1, // multipled by horizon offset to increase/decrease offset for each additional item
sizeMultiplier: 0.7, // determines how drastically the size of each item changes
opacityMultiplier: 0.8, // determines how drastically the opacity of each item changes
horizon: 0, // how "far in" the horizontal/vertical horizon should be set from the container wall. 0 for auto
flankingItems: 3, // the number of items visible on either side of the center
// animation
speed: 300, // speed in milliseconds it will take to rotate from one to the next
animationEasing: 'linear', // the easing effect to use when animating
quickerForFurther: true, // set to true to make animations faster when clicking an item that is far away from the center
edgeFadeEnabled: false, // when true, items fade off into nothingness when reaching the edge. false to have them move behind the center image
// misc
linkHandling: 2, // 1 to disable all (used for facebox), 2 to disable all but center (to link images out)
autoPlay: 0, // indicate the speed in milliseconds to wait before autorotating. 0 to turn off. Can be negative
orientation: 'horizontal', // indicate if the carousel should be 'horizontal' or 'vertical'
activeClassName: 'carousel-center', // the name of the class given to the current item in the center
keyboardNav: false, // set to true to move the carousel with the arrow keys
keyboardNavOverride: true, // set to true to override the normal functionality of the arrow keys (prevents scrolling)
imageNav: true, // clicking a non-center image will rotate that image to the center
// preloader
preloadImages: true, // disable/enable the image preloader.
forcedImageWidth: 0, // specify width of all images; otherwise the carousel tries to calculate it
forcedImageHeight: 0, // specify height of all images; otherwise the carousel tries to calculate it
// callback functions
movingToCenter: $.noop, // fired when an item is about to move to the center position
movedToCenter: $.noop, // fired when an item has finished moving to the center
clickedCenter: $.noop, // fired when the center item has been clicked
movingFromCenter: $.noop, // fired when an item is about to leave the center position
movedFromCenter: $.noop // fired when an item has finished moving from the center
};
})(jQuery);
/*--water_wheel_carousel--*/
var carousel = $("#carousel").waterwheelCarousel({
flankingItems: 2,
separation:200,
sizeMultiplier:0.8,
separationMultiplier:0.9,
speed:250,
});
$("#carousel").swipe( {
swipeStatus:function(event, phase, direction, distance)
{
if (phase=="end"){
if(direction == "right") {
carousel.prev();
}
else if(direction =="left") {
carousel.next();
}else { return false;}
}
},
triggerOnTouchEnd:false,
threshold:100
});
$('#prev').on('click', function (){
carousel.prev();
return false;
});
$('#next').on('click', function (){
carousel.next();
return false;
});
/*--water_wheel carousel_script_end--*/
Kindly help me fix the javascript so as to apply the solution to my code. Thanks
Your images are loading, but there are DIVs with a white background on top of them that are visually blocking the images. If you remove the background-color CSS declaration on those DIVs, then your images will show up. Just update this:
#carousel .caption {
position: absolute;
background-color:#FFF;
top: 0px;
left: 0px;
height:100%;
width:100%;
}
to this:
#carousel .caption {
position: absolute;
top: 0px;
left: 0px;
height:100%;
width:100%;
}

How to create a "Simple Harmonic Oscillation" on scroll event using Javascript

Currently I have a element on the page. I would like that object to oscillate left and right similar to the one in the example give below. However, I would like to bind the oscillation with the browser window scroll event.
https://www.khanacademy.org/computer-programming/simple-harmonic-motion/4920589909229568
I tried this but it's not smooth and accurate:
Javascript
let butterfly_y_position_log = [];
let scroll_times = 0;
let scroll_direction;
$(window).scroll(function(){
let butterfly_y_position = $('.floating-butterfly').offset().top;
let butterfly_x_position;
butterfly_y_position_log.push(butterfly_y_position);
if (butterfly_y_position_log.length > 2){
butterfly_y_position_log = butterfly_y_position_log.slice(1, 4);
}
if (butterfly_y_position_log[0] < butterfly_y_position_log[1] || butterfly_y_position_log[1] == undefined){
scroll_direction = 'down';
scroll_times++;
butterfly_x_position = ($(window).width() / 2) + (100 * Math.sin(2* Math.PI + scroll_times));
} else {
scroll_direction = 'up';
scroll_times--;
butterfly_x_position = ($(window).width() / 2) + (100 * Math.cos(2* Math.PI + scroll_times));
}
console.log(scroll_times);
console.log(butterfly_x_position);
$('.floating-butterfly').css({left:butterfly_x_position});
});
HTML
<img src="..." class="floating-butterfly">
CSS
.floating-butterfly {
position: fixed;
top: 40px;
z-index: 2;
}
The Y-position will remain fixed, only X-position will change on scroll.

Creating a pure javascript range slider control

I managed to create a somewhat working slider control but something feels kind of off. It doesnt quite behave as a normal control should. Sometimes while sliding it gets stuck and well, you might want to see for yourself.
How would you create the slider so that it slides smoothly without interruption or the user needing the cursor exactly on the red track?
function createRange(e) {
var range = (((e.offsetX - 0) * (255 - 0)) / (200-40 - 0)) + 0;
var rounded = Math.round(range);
return rounded;
}
function colorSlider(e) {
createRange(e)
}
var dragging = false;
document.getElementById("knob").addEventListener('mousedown', function(e) {
dragging = true;
e.target.style.pointerEvents = "none"
})
window.addEventListener('mousemove', function(e) {
if (dragging) {
if (createRange(e) <= 255) {
document.getElementById("knob").style.left = e.offsetX + "px"
}
}
})
Here is a fixed version of your slider.
var dragging = false;
var knobOffset = 0;
var track = document.getElementById('track'),
knob = document.getElementById('knob'),
trackWidth = track.offsetWidth,
trackLeft = track.offsetLeft,
trackRight = trackLeft + trackWidth,
knobWidth = knob.offsetWidth,
maxRight = trackWidth - knobWidth; // relatively to track
knob.addEventListener('mousedown', function(e) {
// knob offset relatively to track
knobOffset = e.clientX - knob.offsetLeft;
dragging = true;
});
window.addEventListener('mouseup', function(e) {
dragging = false;
})
window.addEventListener('mousemove', function(e) {
if (dragging) {
// current knob offset, relative to track
var offset = e.clientX - trackLeft - knobOffset;
if(offset < 0) {
var offset = 0;
} else if(offset > maxRight) {
var offset = maxRight;
}
knob.style.left = offset + "px"
}
});
#track {width: 200px;height: 5px; margin:100px; background: red}
#knob {height: 10px; width: 40px; background: black;position: relative; }
<div id='track'>
<div id="knob"></div>
</div>

jQuery draggable with rotation - 'jerking' when dragging when rotated [duplicate]

As an experiment, I created a few div's and rotated them using CSS3.
.items {
position: absolute;
cursor: pointer;
background: #FFC400;
-moz-box-shadow: 0px 0px 2px #E39900;
-webkit-box-shadow: 1px 1px 2px #E39900;
box-shadow: 0px 0px 2px #E39900;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
border-radius: 2px;
}
I then randomly styled them and made them draggable via jQuery.
$('.items').each(function() {
$(this).css({
top: (80 * Math.random()) + '%',
left: (80 * Math.random()) + '%',
width: (100 + 200 * Math.random()) + 'px',
height: (10 + 10 * Math.random()) + 'px',
'-moz-transform': 'rotate(' + (180 * Math.random()) + 'deg)',
'-o-transform': 'rotate(' + (180 * Math.random()) + 'deg)',
'-webkit-transform': 'rotate(' + (180 * Math.random()) + 'deg)',
});
});
$('.items').draggable();
The dragging works, but I am noticing a sudden jump while dragging the div's only in webkit browsers, while everything is fine in Firefox.
If I remove the position: absolute style, the 'jumping' is even worse. I thought there was maybe a difference in the transform origin between webkit and gecko, but they are both at the centre of the element by default.
I have searched around already, but only came up with results about scrollbars or sortable lists.
Here is a working demo of my problem. Try to view it in both Safari/Chrome and Firefox. http://jsbin.com/ucehu/
Is this a bug within webkit or how the browsers render webkit?
I draw a image to indicate the offset after rotate on different browsers as #David Wick's answer.
Here's the code to fix if you don't want patch or modify jquery.ui.draggable.js
$(document).ready(function () {
var recoupLeft, recoupTop;
$('#box').draggable({
start: function (event, ui) {
var left = parseInt($(this).css('left'),10);
left = isNaN(left) ? 0 : left;
var top = parseInt($(this).css('top'),10);
top = isNaN(top) ? 0 : top;
recoupLeft = left - ui.position.left;
recoupTop = top - ui.position.top;
},
drag: function (event, ui) {
ui.position.left += recoupLeft;
ui.position.top += recoupTop;
}
});
});
or you can see the demo
This is a result of draggable's reliance on the jquery offset() function and offset()'s use of the native js function getBoundingClientRect(). Ultimately this is an issue with the jquery core not compensating for the inconsistencies associated with getBoundingClientRect(). Firefox's version of getBoundingClientRect() ignores the css3 transforms (rotation) whereas chrome/safari (webkit) don't.
here is an illustration of the issue.
A hacky workaround:
replace following in jquery.ui.draggable.js
//The element's absolute position on the page minus margins
this.offset = this.positionAbs = this.element.offset();
with
//The element's absolute position on the page minus margins
this.offset = this.positionAbs = { top: this.element[0].offsetTop,
left: this.element[0].offsetLeft };
and finally a monkeypatched version of your jsbin.
David Wick is right about the general direction above, but computing the right coordinates is way more involved than that. Here's a more accurate monkey patch, based on MIT licensed Firebug code, which should work in far more situations where you have a complex DOM:
Instead replace:
//The element's absolute position on the page minus margins
this.offset = this.positionAbs = this.element.offset();
with the less hacky (be sure to get the whole thing; you'll need to scroll):
//The element's absolute position on the page minus margins
this.offset = this.positionAbs = getViewOffset(this.element[0]);
function getViewOffset(node) {
var x = 0, y = 0, win = node.ownerDocument.defaultView || window;
if (node) addOffset(node);
return { left: x, top: y };
function getStyle(node) {
return node.currentStyle || // IE
win.getComputedStyle(node, '');
}
function addOffset(node) {
var p = node.offsetParent, style, X, Y;
x += parseInt(node.offsetLeft, 10) || 0;
y += parseInt(node.offsetTop, 10) || 0;
if (p) {
x -= parseInt(p.scrollLeft, 10) || 0;
y -= parseInt(p.scrollTop, 10) || 0;
if (p.nodeType == 1) {
var parentStyle = getStyle(p)
, localName = p.localName
, parent = node.parentNode;
if (parentStyle.position != 'static') {
x += parseInt(parentStyle.borderLeftWidth, 10) || 0;
y += parseInt(parentStyle.borderTopWidth, 10) || 0;
if (localName == 'TABLE') {
x += parseInt(parentStyle.paddingLeft, 10) || 0;
y += parseInt(parentStyle.paddingTop, 10) || 0;
}
else if (localName == 'BODY') {
style = getStyle(node);
x += parseInt(style.marginLeft, 10) || 0;
y += parseInt(style.marginTop, 10) || 0;
}
}
else if (localName == 'BODY') {
x += parseInt(parentStyle.borderLeftWidth, 10) || 0;
y += parseInt(parentStyle.borderTopWidth, 10) || 0;
}
while (p != parent) {
x -= parseInt(parent.scrollLeft, 10) || 0;
y -= parseInt(parent.scrollTop, 10) || 0;
parent = parent.parentNode;
}
addOffset(p);
}
}
else {
if (node.localName == 'BODY') {
style = getStyle(node);
x += parseInt(style.borderLeftWidth, 10) || 0;
y += parseInt(style.borderTopWidth, 10) || 0;
var htmlStyle = getStyle(node.parentNode);
x -= parseInt(htmlStyle.paddingLeft, 10) || 0;
y -= parseInt(htmlStyle.paddingTop, 10) || 0;
}
if ((X = node.scrollLeft)) x += parseInt(X, 10) || 0;
if ((Y = node.scrollTop)) y += parseInt(Y, 10) || 0;
}
}
}
It's a shame the DOM doesn't expose these calculations natively.
#ecmanaut: Great solution. Thanks for your efforts. To assist others I turned your solution into a monkey-patch. Copy below code to a file. Include the file after loading jquery-ui.js as follows:
<script src="javascripts/jquery/jquery.js"></script>
<script src="javascripts/jquery/jquery-ui.js"></script>
<!-- the file containing the monkey-patch to draggable -->
<script src="javascripts/jquery/patch_draggable.js"></script>
Here's the code to copy/paste into patch_draggable.js:
function monkeyPatch_mouseStart() {
// don't really need this, but in case I did, I could store it and chain
var oldFn = $.ui.draggable.prototype._mouseStart ;
$.ui.draggable.prototype._mouseStart = function(event) {
var o = this.options;
function getViewOffset(node) {
var x = 0, y = 0, win = node.ownerDocument.defaultView || window;
if (node) addOffset(node);
return { left: x, top: y };
function getStyle(node) {
return node.currentStyle || // IE
win.getComputedStyle(node, '');
}
function addOffset(node) {
var p = node.offsetParent, style, X, Y;
x += parseInt(node.offsetLeft, 10) || 0;
y += parseInt(node.offsetTop, 10) || 0;
if (p) {
x -= parseInt(p.scrollLeft, 10) || 0;
y -= parseInt(p.scrollTop, 10) || 0;
if (p.nodeType == 1) {
var parentStyle = getStyle(p)
, localName = p.localName
, parent = node.parentNode;
if (parentStyle.position != 'static') {
x += parseInt(parentStyle.borderLeftWidth, 10) || 0;
y += parseInt(parentStyle.borderTopWidth, 10) || 0;
if (localName == 'TABLE') {
x += parseInt(parentStyle.paddingLeft, 10) || 0;
y += parseInt(parentStyle.paddingTop, 10) || 0;
}
else if (localName == 'BODY') {
style = getStyle(node);
x += parseInt(style.marginLeft, 10) || 0;
y += parseInt(style.marginTop, 10) || 0;
}
}
else if (localName == 'BODY') {
x += parseInt(parentStyle.borderLeftWidth, 10) || 0;
y += parseInt(parentStyle.borderTopWidth, 10) || 0;
}
while (p != parent) {
x -= parseInt(parent.scrollLeft, 10) || 0;
y -= parseInt(parent.scrollTop, 10) || 0;
parent = parent.parentNode;
}
addOffset(p);
}
}
else {
if (node.localName == 'BODY') {
style = getStyle(node);
x += parseInt(style.borderLeftWidth, 10) || 0;
y += parseInt(style.borderTopWidth, 10) || 0;
var htmlStyle = getStyle(node.parentNode);
x -= parseInt(htmlStyle.paddingLeft, 10) || 0;
y -= parseInt(htmlStyle.paddingTop, 10) || 0;
}
if ((X = node.scrollLeft)) x += parseInt(X, 10) || 0;
if ((Y = node.scrollTop)) y += parseInt(Y, 10) || 0;
}
}
}
//Create and append the visible helper
this.helper = this._createHelper(event);
//Cache the helper size
this._cacheHelperProportions();
//If ddmanager is used for droppables, set the global draggable
if($.ui.ddmanager)
$.ui.ddmanager.current = this;
/*
* - Position generation -
* This block generates everything position related - it's the core of draggables.
*/
//Cache the margins of the original element
this._cacheMargins();
//Store the helper's css position
this.cssPosition = this.helper.css("position");
this.scrollParent = this.helper.scrollParent();
//The element's absolute position on the page minus margins
this.offset = this.positionAbs = getViewOffset(this.element[0]);
this.offset = {
top: this.offset.top - this.margins.top,
left: this.offset.left - this.margins.left
};
$.extend(this.offset, {
click: { //Where the click happened, relative to the element
left: event.pageX - this.offset.left,
top: event.pageY - this.offset.top
},
parent: this._getParentOffset(),
relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
});
//Generate the original position
this.originalPosition = this.position = this._generatePosition(event);
this.originalPageX = event.pageX;
this.originalPageY = event.pageY;
//Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
//Set a containment if given in the options
if(o.containment)
this._setContainment();
//Trigger event + callbacks
if(this._trigger("start", event) === false) {
this._clear();
return false;
}
//Recache the helper size
this._cacheHelperProportions();
//Prepare the droppable offsets
if ($.ui.ddmanager && !o.dropBehaviour)
$.ui.ddmanager.prepareOffsets(this, event);
this.helper.addClass("ui-draggable-dragging");
//JWL: Hier vindt de jump plaats
this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
if ( $.ui.ddmanager ) $.ui.ddmanager.dragStart(this, event);
return true;
};
}
monkeyPatch_mouseStart();
I prefer this workaround as it preserves the original handler
It removes the transform then restores it
$(document).ready(function(){
// backup original handler
var _mouseStart = $.ui.draggable.prototype._mouseStart;
$.ui.draggable.prototype._mouseStart = function(event) {
//remove the transform
var transform = this.element.css('transform');
this.element.css('transform', 'none');
// call original handler
var result = _mouseStart.call(this, event);
//restore the transform
this.element.css('transform', transform);
return result;
};
});
demo (started from #Liao San-Kai jsbin)
the answer of David Wick was very helpful... thanks...
here i coded the same workaround for the resizeable, because it has the same problem:
search for the following in jquery.ui.resizable.js
var o = this.options, iniPos = this.element.position(), el = this.element;
and replace with:
var o = this.options, iniPos = {top:this.element[0].offsetTop,left:this.element[0].offsetLeft}, el = this.element;
I used a lot of the solutions to get dragging working correctly. BUT, it still reacted wrong to a dropzone (like it wasn't rotated). The Solution really is to use a parent container that is positioned relative.
This saved me soooo much time.
<div id="drawarea">
<div class="rect-container h">
<div class="rect"></div>
</div>
</div>
.rect-container {
position:relative;
}
Full Solution here (it's not from me):
http://jsfiddle.net/Sp6qa/2/
Also I researched a lot. And its just like this, jQuery doesn't have any plans to change that current behavior in the future. All submitted tickets about that topic were closed. So just start out with having parentcontainers that are positioned relative. It works like a charm and should be futureproof.
You have to set the parent container of the draggable element to "position: relative".

Algorithm for placing dashboard panels in unoccupied space

I'm working on a portal/dashboard type interface which has panels/widgets that can be freely dragged around the dashboard space as long as they don't overlay any other panels. New panels can be added to the dashboard via a menu containing all available panels, and when a menu item is clicked, the panel is placed into the dashboard. The panels currently occupying the dashboard space are all represented in an object like this:
{
'panel_1': { top: 0, left: 0, width: 300, height: 350 },
'panel_2': { top: 0, left: 370, width: 200, height: 275 },
'panel_3': { top: 370, left: 0, width: 275, height: 400 },
...
}
My question is, what is a valid algorithm that would, when the user clicks one in the menu, correctly place a new panel (of a given width and height) in unoccupied space that is closest to a left and top (x and y) value of 0, 0, without overlapping any of the existing panels?
I think, simple bruteforce algorithm will fit you. As I remember, fit rectangle solve another problem
Iterate over your dashboard axis to find out, whether you can place your rectangle, until X < rectangle.widh + dashboard.width, same for Y.
Foreach X,Y on dashboard iterate over every panel to find whether they overlap. You can apply some optimization, to decrease amount of iteration. If panel overlap rectangle, you can increase X or Y(which is in nested loop) not by 1, but by width or height of panel.
In most cases, you will not make dashboard.width*dashboard.height*panel.count iteration. With some optimization, it will find best fit rather quick
I know this is an old question but if anyone wants a proof of concept then it looks like this:
function findSpace(width, height) {
var $ul = $('.snap-layout>ul');
var widthOfContainer = $ul.width();
var heightOfContainer = $ul.height();
var $lis = $ul.children('.setup-widget'); // The li is on the page and we dont want it to collide with itself
for (var y = 0; y < heightOfContainer - height + 1; y++) {
var heightOfShortestInRow = 1;
for (var x = 0; x < widthOfContainer - width + 1; x++) {
//console.log(x + '/' + y);
var pos = { 'left': x, 'top': y };
var $collider = $(isOverlapping($lis, pos, width, height));
if ($collider.length == 0) {
// Found a space
return pos;
}
var colliderPos = $collider.position();
// We have collided with something, there is no point testing the points within this widget so lets skip them
var newX = colliderPos.left + $collider.width() - 1; // -1 to account for the ++ in the for loop
x = newX > x ? newX : x; // Make sure that we are not some how going backwards and looping forever
var colliderBottom = colliderPos.top + $collider.height();
if (heightOfShortestInRow == 1 || colliderBottom - y < heightOfShortestInRow) {
heightOfShortestInRow = colliderBottom - y; // This isn't actually the height its just the distance from y to the bottom of the widget, y is normally at the top of the widget tho
}
}
y += heightOfShortestInRow - 1;
}
//TODO: Add the widget to the bottom
}
function isOverlapping($obsticles, tAxis, width, height) {
var t_x, t_y;
if (typeof (width) == 'undefined') {
// Existing element passed in
var $target = $(tAxis);
tAxis = $target.position();
t_x = [tAxis.left, tAxis.left + $target.outerWidth()];
t_y = [tAxis.top, tAxis.top + $target.outerHeight()];
} else {
// Coordinates and dimensions passed in
t_x = [tAxis.left, tAxis.left + width];
t_y = [tAxis.top, tAxis.top + height];
}
var overlap = false;
$obsticles.each(function () {
var $this = $(this);
var thisPos = $this.position();
var i_x = [thisPos.left, thisPos.left + $this.outerWidth()]
var i_y = [thisPos.top, thisPos.top + $this.outerHeight()];
if (t_x[0] < i_x[1] && t_x[1] > i_x[0] &&
t_y[0] < i_y[1] && t_y[1] > i_y[0]) {
overlap = this;
return false;
}
});
return overlap;
}

Categories