Is there any built in possibily to drag content inside container by dragging for example qx.ui.container.Scroll? I found there are qx.ui.core.MDragDropScrolling and qx.ui.core.DragDropScrolling but don't know that is what I am looking for.
Try the code below (eg copy and paste into the Qooxdoo playground).
It traps the mouse movements and apply the relative movement of the mouse (while the button is down) to the scroll bars
// This is a little hack to persuade Chrome to always show the scroll bars
qx.bom.Stylesheet.createElement(
"::-webkit-scrollbar { -webkit-appearance: none; width: 7px; }\n" +
"::-webkit-scrollbar-thumb { border-radius: 4px; background-color: rgba(0, 0, 0, .5); box-shadow: 0 0 1px rgba(255, 255, 255, .5); }\n"
);
// This is a simple thing to add a border to our bigWidget
var border = new qx.ui.decoration.Decorator().set({
width: 3,
style: "solid",
color: "black"
});
// Creates a massive widget
var bigWidget = new qx.ui.core.Widget().set({
minWidth: 2000,
minHeight: 2000,
backgroundColor: "red",
decorator: border
});
// Scrollable area
var scrollable = new qx.ui.container.Scroll(bigWidget).set({
scrollbar: [ "on", "on" ]
});
var mouseDown = false;
var mouseStartPos = null;
var widgetStartPos = null;
bigWidget.addListener("mousedown", evt => {
mouseDown = true;
mouseStartPos = { top: evt.getScreenTop(), left: evt.getScreenLeft() };
widgetStartPos = { top: scrollable.getScrollX(), left: scrollable.getScrollY() };
});
bigWidget.addListener("mouseup", () => {
mouseDown = false;
});
bigWidget.addListener("mousemove", evt => {
if (!mouseDown || !evt.isLeftPressed())
return;
let deltaPos = { top: mouseStartPos.top - evt.getScreenTop(), left: mouseStartPos.left - evt.getScreenLeft() };
scrollable.scrollToX(widgetStartPos.left - deltaPos.left);
scrollable.scrollToY(widgetStartPos.top - deltaPos.top);
console.log("deltaPos=" + JSON.stringify(deltaPos) + ", mouseStartPos=" + JSON.stringify(mouseStartPos) + ", widgetStartPos=" + JSON.stringify(widgetStartPos));
});
var doc = this.getRoot();
doc.add(scrollable, {
left : 0,
top : 0,
right: 0,
bottom: 0
});
Related
I need to have a cursor to drag and take screenshot of dragged area on HTML webpage. I tried using HTML canvas but it takes screenshot of specific div not the selected region on HTML webpage.
The new html2canvas version 1 has width, height, x and y options.
You can make use of these options to achieve a cropping feature the Firefox's Screenshot's way.
document.onmousedown = startDrag;
document.onmouseup = endDrag;
document.onmousemove = expandDrag;
var dragging = false,
dragStart = {
x: 0,
y: 0
},
dragEnd = {
x: 0,
y: 0
};
function updateDragger() {
dragger.classList.add('visible');
var s = dragger.style;
s.top = Math.min(dragStart.y, dragEnd.y) + 'px';
s.left = Math.min(dragStart.x, dragEnd.x) + 'px';
s.height = Math.abs(dragStart.y - dragEnd.y) + 'px';
s.width = Math.abs(dragStart.x - dragEnd.x) + 'px';
}
function startDrag(evt) {
evt.preventDefault();
dragging = true;
dragStart.x = dragEnd.x = evt.clientX;
dragStart.y = dragEnd.y = evt.clientY;
updateDragger();
}
function expandDrag(evt) {
if (!dragging) return;
dragEnd.x = evt.clientX;
dragEnd.y = evt.clientY;
updateDragger();
}
function endDrag(evt) {
dragging = false;
dragger.classList.remove('visible');
// here is the important part
html2canvas(document.body, {
width: Math.abs(dragStart.x - dragEnd.x),
height: Math.abs(dragStart.y - dragEnd.y),
x: Math.min(dragStart.x, dragEnd.x),
y: Math.min(dragStart.y, dragEnd.y)
})
.then(function(c) {
document.body.appendChild(c);
});
dragStart.x = dragStart.y = dragEnd.x = dragEnd.y = 0;
}
* {
user-select: none;
}
#dragger {
position: fixed;
background: rgba(0, 0, 0, .5);
border: 1px dashed white;
pointer-events: none;
display: none;
}
#dragger.visible {
display: block;
}
canvas {
border: 1px solid;
}
<script src="https://github.com/niklasvh/html2canvas/releases/download/v1.0.0-alpha.1/html2canvas.js"></script>
<div id="wrapper">
<p> Drag to take a screenshot ...</p>
<img crossOrigin src="https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png" width="120" height="120">
</div>
<div id="dragger" tabindex></div>
I'm trying to develop a way to select objects that are layered below and (totally) covered by other objects. One idea is to select the top object and then via doubleclick walk downwards through the layers. This is what I got at the moment:
var canvas = new fabric.Canvas("c");
fabric.util.addListener(canvas.upperCanvasEl, "dblclick", function (e) {
var _canvas = canvas;
var _mouse = _canvas.getPointer(e);
var _active = _canvas.getActiveObject();
if (e.target) {
var _targets = _canvas.getObjects().filter(function (_obj) {
return _obj.containsPoint(_mouse);
});
//console.warn(_targets);
for (var _i=0, _max=_targets.length; _i<_max; _i+=1) {
//check if target is currently active
if (_targets[_i] == _active) {
//then select the one on the layer below
_targets[_i-1] && _canvas.setActiveObject(_targets[_i-1]);
break;
}
}
}
});
canvas
.add(new fabric.Rect({
top: 25,
left: 25,
width: 100,
height: 100,
fill: "red"
}))
.add(new fabric.Rect({
top: 50,
left: 50,
width: 100,
height: 100,
fill: "green"
}))
.add(new fabric.Rect({
top: 75,
left: 75,
width: 100,
height: 100,
fill: "blue"
}))
.renderAll();
canvas {
border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.3/fabric.min.js"></script>
<canvas id="c" width="300" height="200"></canvas>
As you can see, trying to select the red rectangle from within the blue one is not working. I'm only able to select the green or the blue. I guess that after the first doubleclick worked (green is selected), clicking again just selects blue so the following doubleclick is only able to get green again.
Is there a way around this? Any other ideas?
After some time I finally was able to solve that by myself. Clicking on an object brings it to the top. On double-clicking I try to get the object one layer behind the current object. On another dblclick I get the one behind and so on. Works great for me and also allows for the selection of fully covered objects without the need to move others.
var canvas = new fabric.Canvas("c");
canvas.on("object:selected", function (e) {
if (e.target) {
e.target.bringToFront();
this.renderAll();
}
});
var _prevActive = 0;
var _layer = 0;
//
fabric.util.addListener(canvas.upperCanvasEl, "dblclick", function (e) {
var _canvas = canvas;
//current mouse position
var _mouse = _canvas.getPointer(e);
//active object (that has been selected on click)
var _active = _canvas.getActiveObject();
//possible dblclick targets (objects that share mousepointer)
var _targets = _canvas.getObjects().filter(function (_obj) {
return _obj.containsPoint(_mouse) && !_canvas.isTargetTransparent(_obj, _mouse.x, _mouse.y);
});
_canvas.deactivateAll();
//new top layer target
if (_prevActive !== _active) {
//try to go one layer below current target
_layer = Math.max(_targets.length-2, 0);
}
//top layer target is same as before
else {
//try to go one more layer down
_layer = --_layer < 0 ? Math.max(_targets.length-2, 0) : _layer;
}
//get obj on current layer
var _obj = _targets[_layer];
if (_obj) {
_prevActive = _obj;
_obj.bringToFront();
_canvas.setActiveObject(_obj).renderAll();
}
});
//create something to play with
canvas
//fully covered rect is selectable with dblclicks
.add(new fabric.Rect({
top: 75,
left: 75,
width: 50,
height: 50,
fill: "black",
stroke: "black",
globalCompositeOperation: "xor",
perPixelTargetFind: true
}))
.add(new fabric.Circle({
top: 25,
left: 25,
radius: 50,
fill: "rgba(255,0,0,.5)",
stroke: "black",
perPixelTargetFind: true
}))
.add(new fabric.Circle({
top: 50,
left: 50,
radius: 50,
fill: "rgba(0,255,0,.5)",
stroke: "black",
perPixelTargetFind: true
}))
.add(new fabric.Circle({
top: 75,
left: 75,
radius: 50,
fill: "rgba(0,0,255,.5)",
stroke: "black",
perPixelTargetFind: true
}))
.renderAll();
canvas {
border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
<canvas id="c" width="300" height="200"></canvas>
Just add one property during adding object to the canvas.
perPixelTargetFind: true
My task is a bit different - the mission is to pick the overlapping obj behind the current one:
[Left-click]+[Cmd-Key] on an selected-object to pick the first overlapping object which is right behind it.
If no overlapping objs are found, restart the search from top-layer
The idea is, for each click-event, intercept the selected object and replace it with our desired object.
A Fabric mouse-down event is like this:
User clicks on canvas
On canvas: find the target-obj by the coordinates of mouse cursor and stores it in the instance-variable (canvas._target)
Run event-handlers for mouse:down:before
Compare the target-obj found from step(2) with current selected object, fire selection:cleared/update/create events according to the results of comparison.
Set new activeObject(s)
Run event-handlers for mouse:down
We can use a customized event handler on mouse:down:before to intercept the target-obj found on Step(2) and replace it by our desired-object
fCanvas = new fabric.Canvas('my-canvas', {
backgroundColor: '#cbf1f1',
width: 800,
height: 600,
preserveObjectStacking: true
})
const r1 = new fabric.Rect({ width: 200, height: 200, stroke: null, top: 0, left: 0, fill:'red'})
const r2 = new fabric.Rect({ width: 200, height: 200, stroke: null, top: 50, left: 50, fill:'green'})
const r3 = new fabric.Rect({ width: 200, height: 200, stroke: null, top: 100, left: 100, fill:'yellow'})
fCanvas.add(r1, r2, r3)
fCanvas.requestRenderAll()
fCanvas.on('mouse:down:before', ev => {
if (!ev.e.metaKey) {
return
}
// Prevent conflicts with multi-selection
if (ev.e[fCanvas.altSelectionKey]) {
return
}
const currActiveObj = fCanvas.getActiveObject()
if (!currActiveObj) {
return
}
const pointer = fCanvas.getPointer(ev, true)
const hitObj = fCanvas._searchPossibleTargets([currActiveObj], pointer)
if (!hitObj) {
return
}
let excludeObjs = []
if (currActiveObj instanceof fabric.Group) {
currActiveObj._objects.forEach(x => { excludeObjs.push(x) })
} else {
// Target is single active object
excludeObjs.push(currActiveObj)
}
let remain = excludeObjs.length
let objsToSearch = []
let lastIdx = -1
const canvasObjs = fCanvas._objects
for (let i = canvasObjs.length-1; i >=0 ; i--) {
if (remain === 0) {
lastIdx = i
break
}
const obj = canvasObjs[i]
if (excludeObjs.includes(obj)) {
remain -= 1
} else {
objsToSearch.push(obj)
}
}
const headObjs = canvasObjs.slice(0, lastIdx+1)
objsToSearch = objsToSearch.reverse().concat(headObjs)
const found = fCanvas._searchPossibleTargets(objsToSearch, pointer)
if (found) {
fCanvas._target = found
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.3.1/fabric.min.js"></script>
<html>
<h4>Left-click + Cmd-key on overlapping area to pick the obj which is behind current one</h4>
<canvas id="my-canvas"></canvas>
</html>
I need your help.
To apply animation to multiple elements with a single class of "my-button"? Now this only works for a single button.
Replacement querySelector on querySelectorAll not solve the problem, the script becomes not working
Thank you.
var el = document.querySelector('.my-button'),
elSpan = el.querySelector('span'),
// mo.js timeline obj
timeline = new mojs.Timeline(),
scaleCurve = mojs.easing.path('M0,100 L25,99.9999983 C26.2328835,75.0708847 19.7847843,0 100,0'),
// tweens for the animation:
// burst animation
tween1 = new mojs.Burst({
parent: el,
duration: 1500,
shape : 'circle',
fill : [ '#e67e22', '#DE8AA0', '#8AAEDE', '#8ADEAD', '#DEC58A', '#8AD1DE' ],
x: '50%',
y: '50%',
opacity: 0.8,
childOptions: { radius: {20:0} },
radius: {40:120},
angle: {0: 180},
count: 8,
isSwirl: true,
isRunLess: true,
easing: mojs.easing.bezier(0.1, 1, 0.3, 1)
}),
// ring animation
tween2 = new mojs.Transit({
parent: el,
duration: 750,
type: 'circle',
radius: {0: 50},
fill: 'transparent',
stroke: '#2ecc71',
strokeWidth: {15:0},
opacity: 0.6,
x: '50%',
y: '50%',
isRunLess: true,
easing: mojs.easing.bezier(0, 1, 0.5, 1)
}),
// icon scale animation
tween3 = new mojs.Tween({
duration : 900,
onUpdate: function(progress) {
if(progress > 0.3) {
var scaleProgress = scaleCurve(progress);
elSpan.style.WebkitTransform = elSpan.style.transform = 'scale3d(' + scaleProgress + ',' + scaleProgress + ',1)';
elSpan.style.WebkitTransform = elSpan.style.color = '#2ecc71';
} else {
elSpan.style.WebkitTransform = elSpan.style.transform = 'scale3d(0,0,1)';
elSpan.style.WebkitTransform = elSpan.style.color = 'rgba(0,0,0,0.3)';
}
}
});
// add tweens to timeline:
timeline.add(tween1, tween2, tween3);
// when clicking the button start the timeline/animation:
el.addEventListener('mousedown', function() {
timeline.start();
});
.wrapper {
display: flex;
justify-content: center;
align-items: center;
align-content: center;
text-align: center;
height: 200px;
}
.my-button {
background: transparent;
border: none;
outline: none;
margin: 0;
padding: 0;
position: relative;
text-align:center;
}
svg {
top: 0;
left: 0;
}
.send-icon {
position: relative;
font-size: 40px;
color: rgba(0,0,0,0.3);
}
<link href="http://fontawesome.io/assets/font-awesome/css/font-awesome.css" rel="stylesheet"/>
<script src="http://netgon.net/mo.min.js"></script>
<div class="wrapper">
<button class="my-button">
<span class="send-icon fa fa-paper-plane"></span>
</button>
</div>
Codepen
Working CodePen Here!
the "parent element" el must be a single element, so achieving this would be tricky and create a complicated messy code.
so I created a different method that'll create the animations once and set their position using tune() when a button is clicked, this will improve performance since you only have one animation object in the DOM.
instead of creating three animations for each parent/button.
I listen to the all buttons with the class "my-button" set animation's top and left values accordingly
$( ".my-button" ).on( "click", function() {
aniPos = findCenter($(this));
myAnimation1.tune({ top: aniPos.y, left: aniPos.x });
myAnimation2.tune({ top: aniPos.y, left: aniPos.x });
myTimeline.replay();
anipos is calculated with a simple function that'll return an object with .x and .y values
function findCenter ($this) {
var offset = $this.offset();
var width = $this.width();
var height = $this.height();
IDs = {
x: offset.left + (width / 2),
y: offset.top + (height / 2)
};
console.log(offset);
return IDs;
}
I also added a method to animate the clicked button automatically.
elSpan = this.querySelector('span');
new mojs.Tween({
duration : 900,
onUpdate: function(progress) {
if(progress > 0.3) {
var scaleProgress = scaleCurve(progress);
elSpan.style.WebkitTransform = elSpan.style.transform = 'scale3d(' + scaleProgress + ',' + scaleProgress + ',1)';
elSpan.style.WebkitTransform = elSpan.style.color = '#2ecc71';
} else {
elSpan.style.WebkitTransform = elSpan.style.transform = 'scale3d(0,0,1)';
elSpan.style.WebkitTransform = elSpan.style.color = 'rgba(0,0,0,0.3)';
}
}
}).play();
});
please note that my example will require JQuery to work.
I used the exact animations you provided but you can change them as long as you don't change X, Y, top and left properties
The following code creates a draggable 40px rectangle inside a 400px grid (using Interact.js):
HTML:
<div id="app">
<div class="live-demo">
<div id="grid-snap"></div>
</div>
</div>
JS:
var element = document.getElementById('grid-snap')
var x = 0
var y = 0
interact(element)
.draggable({
snap: {
targets: [
interact.createSnapGrid({ x: 10, y: 10 })
],
range: Infinity,
relativePoints: [ { x: 0, y: 0 } ]
},
restrict: {
restriction: element.parentNode,
elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
}
})
.on('dragmove', function (event) {
x += event.dx;
y += event.dy;
event.target.style.webkitTransform =
event.target.style.transform =
'translate(' + x + 'px, ' + y + 'px)';
});
CSS:
.live-demo {
display: inline-block;
vertical-align: top;
width: 400px;
height: 400px;
background-color: #e8e9e8;
}
#grid-snap {
width: 40px;
height: 40px;
background-color: #29e;
color: #fff;
}
For some reason, the square moves an initial 2px when it's dragged for the first time. This doesn't happen when createSnapGrid() is set to 30px for x and y.
Not sure if this is a coding or math problem. How to modify the code (or CSS) to eliminate that initial 2px movement?
JSFiddle: https://jsfiddle.net/nq5zz27j/4/
It is something to do with the math. When you set the relativePoint, it is defining where the grid will snap to. So choosing x: 0, Y: 0 will use the top left corner of your object. As to why x: .8 and y: .8 is the answer. I don't have a scientific answer as to why this is (still working on that part).
But if you change
relativePoints: [ { x: 0, y: 0 } ]
to
relativePoints: [ { x: .8, y: .8 } ]
It will do what you are looking for I believe. Let me know if that is what you meant.
jfiddle: https://jsfiddle.net/nq5zz27j/5/
How can I interact with the tooltip in jQuery?
You know, the little pop-up appearing when you hover an <a> element or an <img>.
I wanted to make that one follow my cursor when I move onto that tag. Exactly like this.
You might wanna look at jQuery UI's tooltip or the QTip plugin.
A part for mouse tracking tooltip: Mouse tracking
I didn't not tried it but it seems nice: one more
Here is simple jquery plugin for custom tooltip. jsFiddle
You can specify mouseFollow: true to achieve movable tooltip that follows cursor.
JS
(function ($) {
$.fn.tooltip = function (options) {
var defaults = {
background: '#fff',
border: '1px solid #999',
color: 'black',
rounded: false,
mouseFollow: false,
textChangeOnClick: false
},
settings = $.extend({}, defaults, options);
this.each(function () {
var $this = $(this),
title = null,
hovering = null;
//set class
if (!settings.textChangeOnClick) {
$this.addClass('_tooltip');
}
$(this).data('title', $this.attr('title'))
$(this).attr('title', '');
$this.hover(
// mouse over
function (e) {
//check change
if ($this.attr('title') != '') {
if ($this.attr('title') != $this.data('title')) {
$this.data('title', $this.attr('title'));
$this.attr('title','');
}
} else {
$this.removeAttr('title');
}
$this.attr('title', '');
hovering = true;
$('#tooltip').remove();
//create box
if ($this.data('title') != '') {
$('<div id="tooltip" />')
.appendTo('body')
.text($this.data('title'))
.hide()
.css({
backgroundColor: settings.background,
color: settings.color,
border: settings.border,
top: e.pageY + 10,
left: e.pageX + 20
})
.fadeIn(500);
}
if (settings.rounded) {
$('#tooltip').addClass('rounded');
}
},
//mouse out
function () {
hovering = false;
$('#tooltip').remove();
});
//text change
if (settings.textChangeOnClick) {
//on click
$this.on('click', function () {
if (hovering) {
//set new
$this.data('title',$(this).attr('title'));
$(this).attr('title', '');
$('#tooltip').text($this.data('title'));
}
});
}
//mouse follow
if (settings.mouseFollow) {
$this.mousemove(function (e) {
$('#tooltip').css({
top: e.pageY + 10,
left: e.pageX + 20
});
});
}
});
return this;
}
})(jQuery);
SET PLUGIN FOR ELEMENT
$('a').tooltip({
mouseFollow: true
});
HTML
CSS
#tooltip
{
border: 1px solid #BFBFBF;
float: left;
font-size: 11px;
max-width: 250px;
padding: 5px;
position: absolute;
color: #464646;
z-index: 999999;
}
.rounded
{
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
}