I have CSS transform scale on the body of my page and some other elements. The position fixed CSS property doesn't work with transform property. I tried to do the same as position fixed but with JavaScript by changing the element's top/bottom value while scrolling. This requires some calculations dynamically as you load the page on different sized screens. Mine works on different screens but when I do window resize on any screen, the fixed div behaves weirdly. It disappears and reappear again. Sometimes it doesn't fixes its position on the intended scroll-y value. I have applied "scroll" and "resize" event listeners for the body/window. I had to do some initial calculations before scroll event, so some functions are under a parent function.
JAVASCRIPT
function chekon()
{
document.addEventListener('DOMContentLoaded', upme);
window.addEventListener('resize', upme);
function upme()
{
var rome = document.getElementById("out-cmnt");
var rect = rome.getBoundingClientRect();
// console.log(rect.top, rect.right, rect.bottom, rect.left);
var poss = rect.top + window.scrollY;
var koss = rect.bottom + window.scrollY; var loss = koss - poss;
var isMobile = !(navigator.userAgentData.mobile);
// event listeners
// window.addEventListener('resize', relod, false);
// function relod() { if(isMobile) { location.reload(); } }
window.addEventListener('scroll', doso, false);
window.addEventListener('resize', doso, false);
function doso()
{
lopp = document.getElementById("Web_1920__1");
hope = lopp.clientHeight;
const meme = document.body.scrollHeight;
const keke = hope/meme;
const scsc = window.scrollY;
var scmx = (document.documentElement.scrollHeight - document.documentElement.clientHeight);
console.log("meme scroll-height = ", meme); console.log("scsc scroll-y = ", scsc);
console.log("scmx max-scroll-y = ", scmx);
var innr = window.innerHeight; console.log("innr inner-height = ", innr);
var scbb = scmx - scsc; var finn = scsc * keke; var nunn = scbb * keke;
if (window.matchMedia("(min-width: 765px)").matches)
{
var finn = scsc * keke * 1.087;
var nunn = scbb * keke * 1.087;
}
var noss = poss - innr + loss;
if(scsc > noss && window.matchMedia("(min-width: 765px)").matches && isMobile)
{
var xoxo = nunn;
document.getElementById("out-cmnt").style.top = "auto";
document.getElementById("out-cmnt").style.bottom = xoxo + "px";
}
if(scsc < noss)
{
document.getElementById("out-cmnt").style.top = "7074px";
}
if(nunn < 100 && isMobile)
{
document.getElementById("last-dab").style.visibility = "hidden";
}
if(nunn > 100 && isMobile)
{
document.getElementById("last-dab").style.visibility = "visible";
}
} }
}
chekon();
Function upme() and doso() is under function chekon() there. The upme() has 2 event listeners and doso() has also 2 event listeners with resize in common. I checked that if doso() resize isn't applied, the upme() resize event listener has no effect on doso() even though doso() is under upme() function. I though, maybe there is overlapping. But seems like fine to me. Is there something messed up in my code that is responsible for the window resize action? The "Web_1920__1" is for getting the total height of the page. The "out-cmnt" is flickering and showing up at wrong place after stopping window resize action for the browser. Then when I start scrolling again the element should get back at its intended position again. But no, staying at wrong position. A reload only fixes the problem for now. Funny thing is, I can't reproduce the wrong position even when the resize is down to at the same window size that showed the problem before. So I think Chrome is showing the problem at random window resizes. Is it browser bug or mine? Help me out please.
You can ignore the variables and calculations. Just care more for the structure, functions and event-listeners like any wrong declarations. Please help me to understand the problem.
I am trying to have it so a my div element, set to "overflow:auto" will scroll when the user is dragging an element.
Dragging the element works, and so does retrieving the proper mouse data (such as the x/y position upon initial drag (onDragStart) and also the current x/y position when the mouse is moving.
I realize my logic for the scrolling probably is off, but I am more just trying to get the div element to scroll at all. Any insight as to what is wrong would be greatly appreciated...Material Design is also being used.
Note: I am using ng-metadata which ports Angular 1 to look like Angular 2, really any guidance in either Angular 1 or Angular 2 would be helpful.
#autobind
onDragStart({clientX, clientY}) {
this.initY = clientY;
if (this.isDragging) return;
document.addEventListener('mousemove', this.dragListener = (e) => {
e.preventDefault();
if (Math.max(Math.abs(e.clientX - clientX), Math.abs(e.clientY - clientY)) > 3) {
document.removeEventListener('mousemove', this.dragListener);
document.addEventListener('mousemove', this.onDrag);
this.dragListener = this.onDrag;
this.fileService.dragSource = this.file;
// onDrag needs to be called before Angular can set the proper classes
this._element.toggleClass('dragging', this.isDragging);
this._element.toggleClass('bulk-dragging', this.inBulkDragOp);
this.onDragScroll(e);
this.onDrag(e);
this.drag.emit();
this._scope.$applyAsync();
}
});
}
#autobind
onDrag(e) {
console.log("Dragging...");
console.log(e);
var currentY = e.clientY;
var range = 100; // Range of 100px before scrolling begins... May need tweaking if too responsive
if (currentY > this.initY + range) {
console.log("SCROLL DOWN");
window.scrollTo(0, 500);
} else if (currentY < this.initY - range) {
console.log("SCROLL UP");
}
e.preventDefault();
this._element.css('transform', `translate(${e.clientX - this._element[0].clientWidth / 2}px, ${e.clientY - this._element[0].clientHeight / 2}px)`);
}
I had my container tag set to a height of 100% which seemed to break the functionality of the scroll. Removing it fixed the problem.
I have a Fabric.js canvas and I want to implement the full-canvas panning that software packages usually do with a "hand" tool. It's when you press one of the mouse buttons, then move over the canvas while holding the mouse button and the visible portion of the canvas changes accordingly.
You can see in this video what I want to achieve.
In order to implement this functionality I wrote the following code:
$(canvas.wrapperEl).on('mousemove', function(evt) {
if (evt.button == 2) { // 2 is the right mouse button
canvas.absolutePan({
x: evt.clientX,
y: evt.clientY
});
}
});
But it doesn't work. You can see in this video what happens.
How can I modify my code in order:
For panning to work like in the first video?
For the event handler to consume the event? It should prevent the context menu from appearing when the user presses or releases the right mouse button.
An easy way to pan a Fabric canvas in response to mouse movement is to calculate the cursor displacement between mouse events and pass it to relativePan.
Observe how we can use the screenX and screenY properties of the previous mouse event to calculate the relative position of the current mouse event:
function startPan(event) {
if (event.button != 2) {
return;
}
var x0 = event.screenX,
y0 = event.screenY;
function continuePan(event) {
var x = event.screenX,
y = event.screenY;
fc.relativePan({ x: x - x0, y: y - y0 });
x0 = x;
y0 = y;
}
function stopPan(event) {
$(window).off('mousemove', continuePan);
$(window).off('mouseup', stopPan);
};
$(window).mousemove(continuePan);
$(window).mouseup(stopPan);
$(window).contextmenu(cancelMenu);
};
function cancelMenu() {
$(window).off('contextmenu', cancelMenu);
return false;
}
$(canvasWrapper).mousedown(startPan);
We start panning on mousedown and continue panning on mousemove. On mouseup, we cancel the panning; we also cancel the mouseup-cancelling function itself.
The right-click menu, also known as the context menu, is cancelled by returning false. The menu-cancelling function also cancels itself. Thus, the context menu will work if you subsequently click outside the canvas wrapper.
Here is a page demonstrating this approach:
http://michaellaszlo.com/so/fabric-pan/
You will see three images on a Fabric canvas (it may take a moment or two for the images to load). You'll be able to use the standard Fabric functionality. You can left-click on the images to move them around, stretch them, and rotate them. But when you right-click within the canvas container, you pan the whole Fabric canvas with the mouse.
I have an example on Github using fabric.js Canvas panning: https://sabatinomasala.github.io/fabric-clipping-demo/
The code responsible for the panning behaviour is the following: https://github.com/SabatinoMasala/fabric-clipping-demo/blob/master/src/classes/Panning.js
It's a simple extension on the fabric.Canvas.prototype, which enables you to toggle 'drag mode' on the canvas as follows:
canvas.toggleDragMode(true); // Start panning
canvas.toggleDragMode(false); // Stop panning
Take a look at the following snippet, documentation is available throughout the code.
const STATE_IDLE = 'idle';
const STATE_PANNING = 'panning';
fabric.Canvas.prototype.toggleDragMode = function(dragMode) {
// Remember the previous X and Y coordinates for delta calculations
let lastClientX;
let lastClientY;
// Keep track of the state
let state = STATE_IDLE;
// We're entering dragmode
if (dragMode) {
// Discard any active object
this.discardActiveObject();
// Set the cursor to 'move'
this.defaultCursor = 'move';
// Loop over all objects and disable events / selectable. We remember its value in a temp variable stored on each object
this.forEachObject(function(object) {
object.prevEvented = object.evented;
object.prevSelectable = object.selectable;
object.evented = false;
object.selectable = false;
});
// Remove selection ability on the canvas
this.selection = false;
// When MouseUp fires, we set the state to idle
this.on('mouse:up', function(e) {
state = STATE_IDLE;
});
// When MouseDown fires, we set the state to panning
this.on('mouse:down', (e) => {
state = STATE_PANNING;
lastClientX = e.e.clientX;
lastClientY = e.e.clientY;
});
// When the mouse moves, and we're panning (mouse down), we continue
this.on('mouse:move', (e) => {
if (state === STATE_PANNING && e && e.e) {
// let delta = new fabric.Point(e.e.movementX, e.e.movementY); // No Safari support for movementX and movementY
// For cross-browser compatibility, I had to manually keep track of the delta
// Calculate deltas
let deltaX = 0;
let deltaY = 0;
if (lastClientX) {
deltaX = e.e.clientX - lastClientX;
}
if (lastClientY) {
deltaY = e.e.clientY - lastClientY;
}
// Update the last X and Y values
lastClientX = e.e.clientX;
lastClientY = e.e.clientY;
let delta = new fabric.Point(deltaX, deltaY);
this.relativePan(delta);
this.trigger('moved');
}
});
} else {
// When we exit dragmode, we restore the previous values on all objects
this.forEachObject(function(object) {
object.evented = (object.prevEvented !== undefined) ? object.prevEvented : object.evented;
object.selectable = (object.prevSelectable !== undefined) ? object.prevSelectable : object.selectable;
});
// Reset the cursor
this.defaultCursor = 'default';
// Remove the event listeners
this.off('mouse:up');
this.off('mouse:down');
this.off('mouse:move');
// Restore selection ability on the canvas
this.selection = true;
}
};
// Create the canvas
let canvas = new fabric.Canvas('fabric')
canvas.backgroundColor = '#f1f1f1';
// Add a couple of rects
let rect = new fabric.Rect({
width: 100,
height: 100,
fill: '#f00'
});
canvas.add(rect)
rect = new fabric.Rect({
width: 200,
height: 200,
top: 200,
left: 200,
fill: '#f00'
});
canvas.add(rect)
// Handle dragmode change
let dragMode = false;
$('#dragmode').change(_ => {
dragMode = !dragMode;
canvas.toggleDragMode(dragMode);
});
<div>
<label for="dragmode">
Enable panning
<input type="checkbox" id="dragmode" name="dragmode" />
</label>
</div>
<canvas width="300" height="300" id="fabric"></canvas>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.15/fabric.min.js"></script>
Not sure about FabricJS, but it could be in such a way:
for it to work like in the first video:
By making use of CSS cursor property, toggling it on mousedown and mouseup events using javascript.
the event handler consume the event (prevent the context menu from appearing, when the user releases the right mouse button):
Using javascript we return false on contextmenu event
CODE: with a little problem ( * )
using jQuery JS Fiddle 1
$('#test').on('mousedown', function(e){
if (e.button == 2) {
// if right-click, set cursor shape to grabbing
$(this).css({'cursor':'grabbing'});
}
}).on('mouseup', function(){
// set cursor shape to default
$(this).css({'cursor':'default'});
}).on('contextmenu', function(){
//disable context menu on right click
return false;
});
Using raw javascript JS Fiddle 2
var test = document.getElementById('test');
test.addEventListener('mousedown', function(e){
if (e.button == 2) {
// if right-click, set cursor shape to grabbing
this.style.cursor = 'grabbing';
}
});
test.addEventListener('mouseup', function(){
// set cursor shape to default
this.style.cursor = 'default';
});
test.oncontextmenu = function(){
//disable context menu on right click
return false;
}
( * ) Problem:
The above snippets works as it should but there's a cross-browser issue, if you check the above fiddles in Firefox - or Opera -you'll see the correct behavior, when checked in Chrome and IE11 - didn't checked it with Edge or Safari - I found that Chrome and IE don't support the grabbing cursor shape, so in the above code snippets, change the cursor lines into this:
jQuery: $(this).css({'cursor':'move'}); JS Fiddle 3
Raw javascript: this.style.cursor = 'move'; JS Fiddle 4
Now we have a working code but without the hand cursor. but there is the following solution:-
SOLUTIONS:
Chrome and Safari support grab and grabbing with the -webkit- prefix like:
$(this).css({'cursor': '-webkit-grabbing'});
but you need to make browser sniffing first, if Firefox then the default and standard code, if Chrome and Safari then with the -webkit- prefix, and this still makes IE out of the game.
Have a look at this example, test it with Chrome, Safari, Firefox, Opera and IE you can see that cursor: url(foo.bar) works and supported in ALL browsers.
Chrome, Safari, Firefox and Opera shows the yellow smile image smiley.gif, but IE shows the red ball cursor url(myBall.cur).
So I think you can make use of this, and a grabbing hand image like this
Or this:
You can use an image like the above, a png or gif format with all browsers except IE which supports .cur, so you need to find a way to convert it into a .cur. Google search shows many result of convert image to cur
Note that, although this cursor:url(smiley.gif),url(myBall.cur),auto; - with fallback support separated by comma, works well in the W3Schools example shown above, I couldn't get it to work the same way in javascript, I tried $(this).css({'cursor': 'grabbing, move'}); but it didn't work.
I also tried doing it as CSS class
.myCursor{ cursor: grabbing, -webkit-grabbing, move; }
Then with jQuery $(this).addClass('myCursor'); but no avail either.
So you still need to make browser sniffing whether you are going the second solution or a hybrid fix of the both solutions, this is my code which I've used couple times to detect browser and it worked well at the time of this post but you porbabely won't need the "Mobile" and "Kindle" parts.
// Detecting browsers
$UA = navigator.userAgent;
if ($UA.match(/firefox/i)) {
$browser = 'Firefox';
} else if ($UA.indexOf('Trident') != -1 && $UA.indexOf('MSIE') == -1) {
$browser = 'MSIE';
} else if ($UA.indexOf('MSIE') != -1) {
$browser = 'MSIE';
} else if ($UA.indexOf('OPR/') != -1) {
$browser = 'Opera';
} else if ($UA.indexOf("Chrome") != -1) {
$browser = 'Chrome';
} else if ($UA.indexOf("Safari")!=-1) {
$browser = 'Safari';
}
if($UA.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Nokia|Mobile|Opera Mini/i)) {
$browser = 'Mobile';
}else if($UA.match(/KFAPWI/i)){
$browser = 'Kindle';
}
console.log($browser);
Resources:
https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
http://www.w3schools.com/cssref/pr_class_cursor.asp
https://css-tricks.com/almanac/properties/c/cursor/
Google images search of grabbing hand cursor
i have made an example on jsfiddle , that we can actually drag the whole canvas with all its objects, into a parent div, like the picture,and i will try to explain it step by step.
First of all i download the drag library jquery.dradscroll.js, you can find it on the net. This is a small js file that with little changes it can helps us complete the task.
download link: http://www.java2s.com/Open-Source/Javascript_Free_Code/jQuery_Scroll/Download_jquery_dragscroll_Free_Java_Code.htm
create the container that holds our canvas.
<div class="content">
<canvas id="c" width="600" height="700" ></canvas>
</div>
little css
.content{
overflow:auto;
width:400px;
height:400px;
}
javascript:
a. create the canvas.
b. make default cursor , when it is over canvas , open hand
canvas.defaultCursor = 'url(" http://maps.gstatic.com/intl/en_us/mapfiles/openhand_8_8.cur") 15 15, crosshair';
c. override the __onMouseDown function , for change to the closedhand cursor( at end).
fabric.Canvas.prototype.__onMouseDown = function(e){
// accept only left clicks
var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1;
if (!isLeftClick && !fabric.isTouchSupported) {
return;
}
if (this.isDrawingMode) {
this._onMouseDownInDrawingMode(e);
return;
}
// ignore if some object is being transformed at this moment
if (this._currentTransform) {
return;
}
var target = this.findTarget(e),
pointer = this.getPointer(e, true);
// save pointer for check in __onMouseUp event
this._previousPointer = pointer;
var shouldRender = this._shouldRender(target, pointer),
shouldGroup = this._shouldGroup(e, target);
if (this._shouldClearSelection(e, target)) {
this._clearSelection(e, target, pointer);
}
else if (shouldGroup) {
this._handleGrouping(e, target);
target = this.getActiveGroup();
}
if (target && target.selectable && !shouldGroup) {
this._beforeTransform(e, target);
this._setupCurrentTransform(e, target);
}
// we must renderAll so that active image is placed on the top canvas
shouldRender && this.renderAll();
this.fire('mouse:down', { target: target, e: e });
target && target.fire('mousedown', { e: e });
if(!canvas.getActiveObject() || !canvas.getActiveGroup()){
flag=true;
//change cursor to closedhand.cur
canvas.defaultCursor = 'url("http://maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur") 15 15, crosshair';
}//end if
override the __onMouseUp event ,to change back the cursor to openhand.
fabric.Canvas.prototype.__onMouseUp = function(e){
if(flag){
canvas.defaultCursor = 'url(" http://maps.gstatic.com/intl/en_us/mapfiles/openhand_8_8.cur") 15 15, crosshair';
flag=false;
}
};
You initialize the dragScroll() to work on the content that hosts the canvas:
$('.content').dragScroll({});
Some small changes on the jquery.dragScroll.js file, so as to understand when to drag the canvas and when not. On mousedown() event we add an if statement to check whether we have an active object or group.If true ,no canvas drag.
$($scrollArea).mousedown(function (e) {
if (canvas.getActiveObject() || canvas.getActiveGroup()) {
console.log('no drag');return;
} else {
console.log($('body'));
if (typeof options.limitTo == "object") {
for (var i = 0; i < options.limitTo.length; i++) {
if ($(e.target).hasClass(options.limitTo[i])) {
doMousedown(e);
}
}
} else {
doMousedown(e);
}
}
});
on mousedown event we grab the DOM element (.content) and get the top & left position
function doMousedown(e) {
e.preventDefault();
down = true;
x = e.pageX;
y = e.pageY;
top = e.target.parentElement.parentElement.scrollTop; // .content
left = e.target.parentElement.parentElement.scrollLeft;// .content
}
If we dont want to have the scrollbars visible:
.content{
overflow:hidden;
width:400px;
height:400px;
}
There is a small problem though,jsfiddle, accepts only https libraries ,so it blocks fabricjs, except if you add it from 'https://rawgit.com/kangax/fabric.js/master/dist/fabric.js', but again it still blocks it some times (at least on my chrome and mozilla).
jsfiddle example : https://jsfiddle.net/tornado1979/up48rxLs/
you may have better luck ,than me, on your browser , but it would definitelly work on your live app.
Anyway,
i hope helps ,good luck.
I know this has already been answered, but I redid the pen created in this answer using the new version of fabricjs (4.5.0)
Pen: https://codepen.io/201flaviosilva/pen/GROLbQa
In this case, I used the middle button in the mouse to pan :)
// Enable mouse middle button
canvas.fireMiddleClick = true;
// Mouse Up Event
if (e.button === 2) currentState = STATE_IDLE;
// Mouse Down Event
if (e.button === 2) currentState = STATE_PANNING;
:)
I wrote a program in which you drag my custom scroll bar and page scrolls accordingly, everything works fine, however while testing it I discovered that if the page is too big 20 000 pixels and you do a large scroll it will crash chrome and opera.
This is caused by window.scroll(0, whereto1); being fired too many times on drag. when i comment this line of code everything works on the large scroll obviously, except for the functionality of scrolling page while dragging.
Below is the relevant code, if anyone has any good idea on how to do this without crashing the browser on large pages let me know. Thank you very much for your time.
var $dragging = null;
var pageheight1 = $( document ).height();
$(document.body).on("mousemove", function(e) {
if ($dragging) {
var wrap = jQuery('.pvs_inner_fixedbar');
wrapoffset = wrap.offset();
wrapbottomborder = ( wrapoffset.top + wrap.height() ) - jQuery('.pvs_scroll_marker').height();
var pos = e.pageY;
if(pos < wrapoffset.top){
pos = wrapoffset.top;
}
if(pos > wrapbottomborder){
pos = wrapbottomborder;
}
$dragging.offset({
top: pos
});
var ratio1 = pageheight1 / 400;
var whereto1 = Math.round((pos - wrapoffset.top) * ratio1) - 100;
window.scroll(0, whereto1); // FIRES TOO MANY TIMES :((((
}
});
$(document.body).on("mousedown", ".pvs_scroll_marker", function (e) {
$dragging = $(e.target);
});
$(document.body).on("mouseup", function (e) {
$dragging = null;
});
You can throttle calls to window.scroll() called from mousemove event handler
https://remysharp.com/2010/07/21/throttling-function-calls
I have a div with a javascript touchmove event listener that scrolls the image inside the div horizontally on iOS6 Mobile Safari. I'd like allow vertical scrolling of the page to bubble up to the browser but when this occurs, jQuery.animate no longer works.
I've posted a simplified version of the code that demonstrates the problem at
https://gist.github.com/4047733
The steps I take to recreate the problem are:
Swipe picture left/right and notice how it animates back to the left edge
Touch the picture and scroll the page up/down
Repeat left/right swipe and notice the picture does NOT animate back to the left edge. It appears jQuery animate fails after touchmove occurs without e.preventDefault
Here is the javascript inside jQuery document ready from the gist link above
var el = document.getElementById("swipebox"),
$slider = $('#swipebox').find('img'),
startX, startY, dx, dy,
startLeft,
animateH = false,
animateV = false;
var onTouchStart = function(e) {
startLeft = parseInt($slider.css('left'), 10) || 0;
startX = e.touches[0].pageX;
startY = e.touches[0].pageY;
};
var onTouchMove = function(e) {
dx = e.touches[0].pageX - startX;
dy = e.touches[0].pageY - startY;
if (
animateH ||
(!animateV && Math.abs(dx) > 5)
) {
// prevent default, we are scrolling horizontally,
animateH = true;
$slider.stop().css({'left': startLeft+dx*2});
e.preventDefault();
} else if (Math.abs(dy) > 5) {
// do NOT prevent default, we are scrolling the page vertically
animateV = true;
} else {
// direction of scroll is undetermined at this time
// we've moved less than 5px in any direction
e.preventDefault();
}
return false;
};
var onTouchEnd = function(e) {
$slider.stop().animate({'left': 0}); // animate image back to left
e.preventDefault();
animateH = false;
animateV = false;
};
var onTouchCancel = function(e) {
console.log('onTouchCancel');
};
el.addEventListener('touchstart', onTouchStart, false);
el.addEventListener('touchmove', onTouchMove, false);
el.addEventListener('touchend', onTouchEnd, false);
el.addEventListener("touchcancel", onTouchCancel, false);
Any insight would be greatly appreciated.
This is bug in iOS6. jQuery animate timers fail when scrolling window in iOS6.
Currently there are few workarounds on this:
Create your own timer functions like someone did: https://gist.github.com/3755461
Use CSS3 transition instead of jQuery.animate. This is preffered way - css3 transitions doesn't have such problem. You can use this jquery plugin http://ricostacruz.com/jquery.transit/ to easily manipulate CSS transitions in JavaScript.