Angular2 :How to make a movable svg object with hammer.js? - svg

i'm Working on an angular2 app, and i'm using hammer.js to make movable a simple rectangle svg object in the view of my component , the methode i uses consists on using the "pan action " of hammer which gives me the x and y position of every deplacing clich mouvement , and i'm affecting thos x and y into the x and y attribtes of my svg rectangle , the problem is that this affectation is not done even i'm seeing in the console the value of x and y of the action ,
any idea to pass the value of depalcement into the svg element postion ???
this is my code :
the view:
<div>
<svg class="simulation">
<rect id="test1" x="{{NX}}" y="{{NY}}" height="150" width="200"
style="stroke:#EC9A20;stroke-width: 15; fill: none"/>
</svg>
</div>
and the TS.file :
export class ContentComponent implements AfterViewInit{
static hammerInitialized = false;
public NX:any ;
public NY:any ;
ngAfterViewInit():any {
console.log('in ngAfterViewInit');
if (!ContentComponent.hammerInitialized) {
var myElement = document.getElementById('test1');//indiquer ici l'element
var animation = new Hammer(myElement);
animation.get('pan').set({direction:Hammer.DIRECTION_ALL})
animation.on("panleft panright panup pandown tap press",
(ev):any => {
myElement.textContent = ev.type +" gesture detected.";
console.log(ev);
this.NX=ev.center.x;
console.log("NX="+this.NX) // this show successfully the value of the action movement NX
this.NY=ev.center.y;
console.log("NY="+this.NY) // this show successfully the value of the action movement NY
}
);
ContentComponent.hammerInitialized = true;
}
else {
console.log('hammer already initialised');
}
console.log("NX="+this.NX); //here it shows me that NX is undefined
console.log("NY="+this.NY) //undefined too
}
}

that worked by not passing the hammer function as a static param
ngAfterViewInit() {
/////////////////////////////////////////////////
//reference à l'element svg
var myElement = document.getElementById('idsimulation');
var actionHammer = new Hammer(myElement);
//action pan
actionHammer.get('pan').set({direction:Hammer.DIRECTION_ALL});
//actionHammer.on("panleft panright panup pandown tap press",hammer);
//actionHammer.on('pan',hammer);
actionHammer.on('pan',ev => {
//console.log(ev);
console.log("X=" + ev.center.x);
console.log("Y=" + ev.center.x);
this.ParamService.dormant.X0 = (ev.center.x) - 200; //170
this.ParamService.dormant.Y0 = (ev.center.y) - 190; //150
// myElement.setAttribute("x", ev.center.x+200);
// myElement.setAttribute("y", ev.center.y+270);
});
//action pinch
actionHammer.get('pinch').set({enable:true});
actionHammer.on('pinch',ev =>{
console.log(ev);
})
}

Related

Maintaining object size AND position while zooming in fabric js

I was trying to maintain the object size while zooming, i tried to get inspired by this answer in which the guy who wrote it didn't solve the controls issue in such as case, as a consequence you can see them not sticking to the object while zooming as in this screenshot.
But i came with this solution to maintain the object position and controls by updating its left and top after calculating them based on the inverted viewportTransform by calculating a new fabric.Point using the fabric.util.transformPoint function
fabric.Object.prototype.transform = function(ctx) {
const obj = this;
const {
ignoreZoom,
group,
canvas,
left,
top
} = obj;
const {
contextTop,
viewportTransform,
getZoom,
requestRenderAll,
} = canvas;
var needFullTransform = (group && !group._transformDone) || (group && canvas && ctx === contextTop);
if (ignoreZoom) {
const oldP = new fabric.Point(left, top);
const newP = fabric.util.transformPoint(oldP, fabric.util.invertTransform(viewportTransform));
var zoom = 1 / getZoom();
/* // here i tried to refresh the whole canvas with requestRenderAll()
this.set({
left: newP.x,
top: newP.y,
scaleX: zoom,
scaleY: zoom,
});
this.setCoords();
requestRenderAll();
*/
// but here i try refresh the object only which is better i think
this.left = newP.x;
this.top = newP.y;
this.scaleX = zoom;
this.scaleY = zoom;
this.drawObject(ctx);
}
var m = this.calcTransformMatrix(!needFullTransform);
ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
}
I have made this codesandbox as a demo for my code. As you can see in this screenshot, controls stick around the object but the whole of them doesn't maintain their position relatively to the background and sometimes they disappear completely.
I need the object to keep its position relatively to the background.
How to make it better ?
// EDIT
I tried to understand better what happens while zooming, i found the fabric.Canvas.zoomToPoint() which is used for zooming (as in their tutorial)
zoomToPoint: function (point, value) {
// TODO: just change the scale, preserve other transformations
var before = point, vpt = this.viewportTransform.slice(0);
point = transformPoint(point, invertTransform(this.viewportTransform));
vpt[0] = value;
vpt[3] = value;
var after = transformPoint(point, vpt);
vpt[4] += before.x - after.x;
vpt[5] += before.y - after.y;
return this.setViewportTransform(vpt);
},
i guess the best way to fix the object position relatively to the background will be to apply the inverse transformation of the one applied to the canvas for the zoom to the object.
So i wrote this function
function getNewVpt(point, value) {
var before = point,
vpt = canvas.viewportTransform.slice(0);
point = fabric.util.transformPoint(point, fabric.util.invertTransform(canvas.viewportTransform));
vpt[0] = value;
vpt[3] = value;
var after = fabric.util.transformPoint(point, vpt);
vpt[4] += before.x - after.x;
vpt[5] += before.y - after.y;
return vpt;
}
and i used it to rewrite the fabric.Object.prototype.transform
fabric.Object.prototype.transform = function (ctx) {
const obj = this;
const { ignoreZoom, group, canvas: objCanvas, left, top } = obj;
const {
contextTop,
viewportTransform,
} = objCanvas;
var needFullTransform =
(group && !group._transformDone) ||
(group && objCanvas && ctx === contextTop);
if (ignoreZoom && zoomingIsOn) {
zoomingIsOn = false;
var zoom = 1 / objCanvas.getZoom();
const oldP = new fabric.Point(left, top);
console.log('transform : oldP : ', oldP);
const newVpt = getNewVpt(oldP, zoom)
const newP = fabric.util.transformPoint(oldP, newVpt);
console.log('transform : newP : ', newP);
// here i tried to refresh the whole canvas with requestRenderAll()
this.set({
left: newP.x,
top: newP.y,
scaleX: zoom,
scaleY: zoom
});
this.setCoords();
console.log('transform : CALLING objCanvas.requestRenderAll() ');
objCanvas.requestRenderAll();
// but here i try refresh the object only which is better i think
// this.left = newP.x;
// this.top = newP.y;
// this.scaleX = zoom;
// this.scaleY = zoom;
// this.drawObject(ctx);
}
var m = this.calcTransformMatrix(!needFullTransform);
ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
};
And here i forked this new codesandbox for this second solution , the result seems to be better than the former solution but it still not perfect. What i may still be doing wrong ?!
// EDIT 2
I tried to pass objCanvas.getZoom() instead of zoom as second parameter to the getNewVpt() function. It seems there is some more improovement but still not perfect again
// Edit 3
In This codesandbox probably i got the best result i could get using another function which returns directly the new point:
function getNewPt(point, value) {
// TODO: just change the scale, preserve other transformations
var vpt = canvas.viewportTransform.slice(0);
point = fabric.util.transformPoint(point, fabric.util.invertTransform(canvas.viewportTransform));
vpt[0] = value;
vpt[3] = value;
return fabric.util.transformPoint(point, vpt);;
}
I still wish anybody who can tell me if there is a way to improove it more. As you can see the triangle returns back to its initial position after zooming/ dezooming and getting back to the same initial zoom value which is good but between those initial and final states , it still seems not to be in the right spot..
You just have to call zoomToPoint where it zooms and the objects will keep their position and scale relative to the background.
Try the following
canvas.on('mouse:wheel', function(opt) {
// console.log(opt.e.deltaY)
let zoomLevel = canvas.getZoom();
// console.log('zoom Level: ', (zoomLevel * 100).toFixed(0), '%');
zoomLevel += opt.e.deltaY * -0.01;
// Restrict scale
zoomLevel = Math.min(Math.max(.125, zoomLevel), 20);
canvas.zoomToPoint(
new fabric.Point(opt.e.offsetX, opt.e.offsetY),
zoomLevel,
);
canvas.renderAll();
})

How to get the calculated y value of a tspan or text svg element?

I need to know the calculated y value (vertical baseline position) of each text and tspan. But writing a function to traverse the tree and compute the calculated y value has been complicated.
<svg height="500">
<text>
<tspan x="0" y="17.6">Compare the way things are going in</tspan>
<tspan x="0" dy="1.1em">the United States with how they were</tspan>
<tspan x="0" y="17.6" dy="1.1em">going five years ago.</tspan>
<tspan x="0">Something else</tspan>
</text>
</svg>
Is there any shortcut for this?
A clearer question might be: "How can I calculate y attributes for all texts and tspans that allow me remove all dy values and still have the SVG render exactly the same way"?
Note that getBBox().y and getStartPositionOfChar(n).y do nor return baseline position.
Here is something that might work for y if the element can be mutated. Something similar could be made for x.
function calcualteXY(el: SVGTextElement) {
const dominantBaseline = getAttribute(el, "dominant-baseline");
const alignmentBaseline = getAttribute(el, "alignment-baseline");
if (dominantBaseline) el.removeAttribute("dominant-baseline");
if (alignmentBaseline) el.removeAttribute("alignment-baseline");
el.setAttribute("y", el.getStartPositionOfChar(0).y.toString());
if (dominantBaseline) el.setAttribute("dominant-baseline", dominantBaseline);
if (alignmentBaseline) el.setAttribute("alignment-baseline", alignmentBaseline);
}
Best attempt so far
type ElementWithAttribute = {
element: Element;
attribute: keyof typeof svgAttributeToCssStyle;
value: string;
};
const hAlignAttributes: (keyof typeof svgAttributeToCssStyle)[] = ["text-anchor"];
const vAlignAttributes: (keyof typeof svgAttributeToCssStyle)[] = ["dominant-baseline", "alignment-baseline"];
function calcualteX(svg: SVGSVGElement) {
const elementsWithAttributes: ElementWithAttribute[] = [];
for (const attr of hAlignAttributes) {
const elements = querySelectorAll<SVGElement>(svg, `[${attr}]`);
for (const element of elements) {
const val = getAttribute(element, attr);
if (val !== null) {
elementsWithAttributes.push({ element, attribute: attr, value: val });
element.removeAttribute(attr);
}
}
}
const elements = querySelectorAll<SVGTSpanElement>(svg, `tspan`);
for (const element of elements) {
element.setAttribute("x", element.getStartPositionOfChar(0).y.toString());
element.removeAttribute("dx");
}
for (const elementWithAttribute of elementsWithAttributes) {
elementWithAttribute.element.setAttribute(elementWithAttribute.attribute, elementWithAttribute.value);
}
}
function calcualteY(svg: SVGSVGElement) {
const elementsWithAttributes: ElementWithAttribute[] = [];
for (const attr of vAlignAttributes) {
const elements = querySelectorAll<SVGElement>(svg, `[${attr}]`);
for (const element of elements) {
const val = getAttribute(element, attr);
if (val !== null) {
elementsWithAttributes.push({ element, attribute: attr, value: val });
element.removeAttribute(attr);
}
}
}
const elements = querySelectorAll<SVGTSpanElement>(svg, `tspan`);
for (const element of elements) {
element.setAttribute("y", element.getStartPositionOfChar(0).y.toString());
element.removeAttribute("dy");
}
for (const elementWithAttribute of elementsWithAttributes) {
elementWithAttribute.element.setAttribute(elementWithAttribute.attribute, elementWithAttribute.value);
}
}

How to get Pixi Particles to behave properly when set as a child of a slot inside of a spine animation

protected createParticleAnimation ( data: IAnticipationParticle ): particles.Emitter {
let particleResource = PIXI.loader.resources[ data.name ].data;
let particleImages: Array<Texture> = new Array<Texture>();
let container = new particles.ParticleContainer();
container.scale.set( 1, -1 );
let spineAnim = this.anticipationAnimations[ 0 ] as spine.Spine;
spineAnim.slotContainers.forEach( ( slotContainer: Container ) => {
if ( slotContainer.name == 'CoinParticles' ) {
container.position.set( slotContainer.children[ 0 ].x * 2, slotContainer.children[ 0 ].y * 2 );
slotContainer.addChild( container );
return;
}
} );
data.images.forEach( ( image: string ) => {
particleImages.push( PIXI.Texture.fromImage( image ) );
} );
let animation = new PIXI.particles.Emitter(
container,
particleImages,
particleResource
);
animation.emit = false;
animation.autoUpdate = true;
return animation;
}
So I create my spine animation in another function then create my particle effect as shown above and attach it to a slotContainer inside of my spine animation. But when my spine animation plays the particles always follow the parents position and do not keep their world coordinates.
I believe this is because of spine but does anyone have any ideas on how to get around this?
So for example when the emitter moves the particles that have already been generate follow the x position of the emitter
So I ended up needing to override the updateTransform method of the pixi container.
I passed the emitter to the container so it could update its transform.
I needed to calculate the original point of the sprite when I did this.
This needs to be called after the emitter is created because the container needs to passed into the creation of the emitter.
Then you need to move the containers transform back to where it was originally so the children ( the particles ) can behave properly. You then move the emitters spawn position along with the parent.
import { particles, Point } from 'pixi.js';
/**
* A container to be used when attaching a particle emitter to a spine animation
*/
export class SpineParticleContainer extends particles.ParticleContainer {
/**The emitter that is drawning to the container */
protected emitter: particles.Emitter;
/**The original position of the sprite when the emitter is set */
protected origin: Point;
constructor () {
super();
}
/**
* Sets the containers emittter so it can update the emitters position
* #param emiter The particle emitter to pass in, this should be the particle emitter assigned to this container already
*/
public setEmitter ( emiter: particles.Emitter ): void {
this.emitter = emiter;
this.origin = new Point( this.parent.worldTransform.tx, this.parent.worldTransform.ty );
}
/**Override update transform to reposition the container at its origin position and move the emitter along with the animation */
public updateTransform () {
this._boundsID++;
this.worldAlpha = this.alpha * this.parent.worldAlpha;
let position = new Point( this.parent.worldTransform.tx, this.parent.worldTransform.ty );
let newPosition = new Point( this.origin.x - position.x, this.origin.y - position.y );
this.position.set( newPosition.x, newPosition.y );
this.transform.updateTransform( this.parent.transform );
for ( let i = 0, j = this.children.length; i < j; i++ ) {
const child = this.children[ i ];
if ( child.visible ) {
child.updateTransform();
}
}
this.emitter.spawnPos.set( this.parent.worldTransform.tx, this.parent.worldTransform.ty );
}
}

Dynamic transition component VueJS 2

I would like to be able to switch between transition A and B dynamically
Parent component
<child></child>
Child component
Transition A
<transition name="fade">
<p v-if="show">hello</p>
</transition>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s
}
.fade-enter, .fade-leave-to /* .fade-leave-active in <2.1.8 */ {
opacity: 0
}
Transition B (using animate.css library)
<transition
name="custom-classes-transition"
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight"
>
<p v-if="show">hello</p>
</transition>
You can do the dynamic transition with properly assigning the enterClass and leaveClass. Here is the working demo: https://fiddle.jshell.net/j9h3Lmr5/1/
JS:
var vm = new Vue({
el: '#vue-instance',
data: {
show: true,
transitionType: "fade",
enterClass: "fade-enter",
leaveClass: "fade-enter-active"
},
methods: {
changeTransition() {
if (this.transitionType === "fade") {
this.transitionType = "custom-classes-transition"
this.enterClass = "animated slideInUp"
this.leaveClass = "animated slideOutDown"
} else {
this.transitionType = "fade"
this.enterClass = "fade-enter"
this.leaveClass = "fade-enter-active"
}
}
}
});

YUI3 scrollView and mousewheel

I'm starting to work only with YUI3. I include component scrollView, but it did not work mousewheel event, in the options I have not found how to turn on it. I would appreciate any help.
var scrollView = new Y.ScrollView({
id: "scrollview",
srcNode: '.scrollview-item',
height: 375,
flick: {
minDistance: 10,
minVelocity: 0.3,
axis: "y"
}
});
scrollView.render();
I stumbled upon this as well, after some trial and error, I managed to get that working(note, that it is just plain scrolling, without an easing).
var DOM_MOUSE_SCROLL = 'DOMMouseScroll',
fixArgs = function(args) {
var a = Y.Array(args, 0, true), target;
if (Y.UA.gecko) {
a[0] = DOM_MOUSE_SCROLL;
// target = Y.config.win;
} else {
// target = Y.config.doc;
}
if (a.length < 3) {
// a[2] = target;
} else {
// a.splice(2, 0, target);
}
return a;
};
Y.Env.evt.plugins.mousewheel = {
on: function() {
return Y.Event._attach(fixArgs(arguments));
},
detach: function() {
return Y.Event.detach.apply(Y.Event, fixArgs(arguments));
}
};
This is the YUI mousewheel event, but it's changed a bit. The biggest issue was, that originally, either the window or document elements, which makes no sense(for example when you mousewheel over the #myelement you want that to be the returned target..)
Bellow is the code used to initialize the ScrollView and the function that handles the mousewheel event:
// ScrollView
var scrollView = new Y.ScrollView({
id: "scrollview",
srcNode: '#mycontainer',
height: 490,
flick: {
minDistance:10,
minVelocity:0.3,
axis: "y"
}
});
scrollView.render();
var content = scrollView.get("contentBox");
var scroll_modifier = 10; // 10px per Delta
var current_scroll_y, scroll_to;
content.on("mousewheel", function(e) {
// check whether this is the scrollview container
if ( e.currentTarget.hasClass('container') ) {
current_scroll_y = scrollView.get('scrollY');
scroll_to = current_scroll_y - ( scroll_modifier * e.wheelDelta );
// trying to scroll above top of the container - scroll to start
if ( scroll_to <= scrollView._minScrollY ) {
// in my case, this made the scrollbars plugin to move, but I'm quite sure it's important for other stuff as well :)
scrollView._uiDimensionsChange();
scrollView.scrollTo(0, scrollView._minScrollY);
} else if ( scroll_to >= scrollView._maxScrollY ) { // trying to scroll beneath the end of the container - scroll to end
scrollView._uiDimensionsChange();
scrollView.scrollTo(0, scrollView._maxScrollY);
} else { // otherwise just scroll to the calculated Y
scrollView._uiDimensionsChange();
scrollView.scrollTo(0, scroll_to);
};
// if we have scrollbars plugin, flash the scrollbar
if ( scrollView.scrollbars ) {
scrollView.scrollbars.flash();
};
// prevent browser default behavior on mouse scroll
e.preventDefault();
};
});
So basically that's how I managed to that, but my next challenge is to get the scrollbar work like regular scrollbar(when you drag it, the container should move correspondingly...)
Hope this helps anyone :)

Resources