How do you associate DOM elements with tween.js? - tween.js

How do you relate TWEEN.js to a DOM element to do some complicated (or simple) animation effects on this DOM element?
I got a demo on the Internet, you need to create a sophisticated animation (using three.js) and DOM (to show or hide the elements associated with a TWEEN.js to DOM elements, DOM elements) has been written in the inside of the HTML (just slow to show or hide the effect).
The implementation of the clickMeOk method has achieved animation effects, but I hope to perform another effect at the same time - the display or hiding of text descriptions (shown or hidden with animation)
var isMeTweening = false;
function clickMeOk() {
if (isMeTweening)
return;
isMeTweening = true;
var scale = mesh6.scale.x < 1 ? 1 : 0.001;
new TWEEN.Tween(mesh6.scale)
.to({ x: scale, y: scale, z: scale }, 2000)
.easing(TWEEN.Easing.Quartic.InOut)
.onComplete(function() {
isMeTweening = false;
}).start();
var opacity = mesh6.material.opacity > 0 ? 0 : 0.5;
new TWEEN.Tween(mesh6.material).to({ opacity: opacity }, 1800).easing(TWEEN.Easing.Quartic.InOut).start();
//Here you want to add DOM element animation (display or hide)
}
Thanks !

I think You should use CSS to make animations on DOM Elements.
https://www.w3schools.com/css/css3_animations.asp

Related

Update path of existing SVG element

I am trying to draw a border on svg element using code.
elem.attr({
'stroke-width' : 2,
'stroke' : '#3fa9f5'
});
elem is already drawn and I dont have control how its drawn . When I see its path ie elem.d , Z is not present at end. Because of that I am not able to draw border at one end.
elem.d="M 323.5 8 L 323.5 40 409.5 40 409.5 8"
Can I dynamically add Z to the above element? Adding Z to elem.d string is not working.
Code for adding Z to elem.d
if(elem.d !== undefined){
if(elem.d.indexOf("Z") === -1){
elem.d += " Z";
}
}
There's no elem.d property. The interface is SVGAnimatedPathData
So it's elem[0].pathSegList.appendItem to append the Z. And createSVGPathSegClosePath to create it which means putting it all together looks like this.
elem[0].pathSegList.appendItem(elem[0].createSVGPathSegClosePath());
The [0] unwraps the jquery wrapper on the element (if elem is a raw DOM element object then the [0] is not necessary)
If you want to test whether the last segment is a close path then you want something like this...
var pathSegList = elem[0].pathSegList;
if (pathSegList.numberOfItems > 0) {
var lastSeg = pathSegList.getItem(pathSegList.numberOfItems - 1);
if (lastSeg.pathSegType != lastSeg.PATHSEG_CLOSEPATH) {
// do whatever
}
}
Alternatively you could do with with attr (jquery) or get/setAttribute (DOM)
elem.attr(d) will get the attribute as a string as will elem[0].getAttribute("d") The SVG DOM above will give better perfomance though.

resize SVG Element: best practice with multiple items

What is the best practice for resizing say an Ellipse or Rectangle on another SVG element?
If I check for the onMouseMove event on the ellipse when I am out of it, the resizing stops. I was thinking to implement the listener on the svg element as well and pass the information to the ellipse I started the resizing on. This means having a generic Resize() method on the svg element that passes to the Resize() of the selected ellipse.
Isn't there an easier way?
I'm currently doing it with Dart, but it should be the same with javascript.
An example is an <svg> element with a <g> containing <rect> and an <ellipse>. If I start to resize the rectangle on a rectangle.onMouseMove, the moment I'm outside it, it stops resizing. To avoid this, I have to use the svg.onMouseMove, and use a resize method that resizes the rectangle. To know that it's the rectangle to be resized, I check for onMouseDown and again check the target (MouseEvent.target on Dart. Not sure how to detect what I'm touching without doing a cumbersome check on id maybe). Note that what I am trying to accomplish is to use the rectangle to resize the ellipse. I'm showing the rectangle only when resizing.
The following code will create a circle and resize when you start dragging.
It also works when the mouse gets outside the circle.
Maybe you can modify this to your liking.
Oh, and my Dart-code might not be the best, as I just recently started learning Dart.
Any improvements are welcome.
import 'dart:html';
import 'dart:svg';
import 'dart:math' as math;
void main() {
// add new svg
var svg = new SvgSvgElement();
svg.style.width = '400px';
svg.style.height = '400px';
svg.style.border = '1px solid black';
var body = document.querySelector('body');
body.append(svg);
// add group
var g = new SvgElement.tag('g');
svg.append(g);
// center of circle
var center = new math.Point(200, 200);
var startR = 70;
var newR = 70;
// add circle
var circle = new CircleElement();
circle.setAttribute('cx', center.x.toString());
circle.setAttribute('cy', center.y.toString());
circle.setAttribute('r', startR.toString());
circle.setAttribute('fill', 'green');
g.append(circle);
circle.onMouseDown.listen((MouseEvent E) {
var startOffset = E.offset;
var startDistance = startOffset.distanceTo(center);
math.Point offset = E.offset;
// now start listening for document movements so we don't need to stay on the circle
var move = document.onMouseMove.listen((MouseEvent E) {
// calculate new position
var movement = E.movement;
offset = new math.Point(
// multiply with 0.5 to make the mouse move faster than the circle grows
// that way we show that the mouse movement also works outside the circle element
offset.x + movement.x * 0.5,
offset.y + movement.y * 0.5
);
// calculate new distance from center
var distance = offset.distanceTo(center);
// calculate new radius for circle
newR = distance / startDistance * startR;
// resize circle
circle.setAttribute('r', newR.toString());
});
// and stop all listening on mouseup
var up = document.onMouseUp.listen(null);
up.onData((MouseEvent E) {
move.cancel();
up.cancel();
startR = newR;
});
});
}
Hope this helps,
Kind regards,
Hendrik Jan

How to avoid the overlapping of text elements on the TreeMap when child elements are opened in D3.js?

I created a Tree in D3.js based on Mike Bostock's Node-link Tree. The problem I have and that I also see in Mike's Tree is that the text label overlap/underlap the circle nodes when there isn't enough space rather than extend the links to leave some space.
As a new user I'm not allowed to upload images, so here is a link to Mike's Tree where you can see the labels of the preceding nodes overlapping the following nodes.
I tried various things to fix the problem by detecting the pixel length of the text with:
d3.select('.nodeText').node().getComputedTextLength();
However this only works after I rendered the page when I need the length of the longest text item before I render.
Getting the longest text item before I render with:
nodes = tree.nodes(root).reverse();
var longest = nodes.reduce(function (a, b) {
return a.label.length > b.label.length ? a : b;
});
node = vis.selectAll('g.node').data(nodes, function(d, i){
return d.id || (d.id = ++i);
});
nodes.forEach(function(d) {
d.y = (longest.label.length + 200);
});
only returns the string length, while using
d.y = (d.depth * 200);
makes every link a static length and doesn't resize as beautiful when new nodes get opened or closed.
Is there a way to avoid this overlapping? If so, what would be the best way to do this and to keep the dynamic structure of the tree?
There are 3 possible solutions that I can come up with but aren't that straightforward:
Detecting label length and using an ellipsis where it overruns child nodes. (which would make the labels less readable)
scaling the layout dynamically by detecting the label length and telling the links to adjust accordingly. (which would be best but seems really difficult
scale the svg element and use a scroll bar when the labels start to run over. (not sure this is possible as I have been working on the assumption that the SVG needs to have a set height and width).
So the following approach can give different levels of the layout different "heights". You have to take care that with a radial layout you risk not having enough spread for small circles to fan your text without overlaps, but let's ignore that for now.
The key is to realize that the tree layout simply maps things to an arbitrary space of width and height and that the diagonal projection maps width (x) to angle and height (y) to radius. Moreover the radius is a simple function of the depth of the tree.
So here is a way to reassign the depths based on the text lengths:
First of all, I use the following (jQuery) to compute maximum text sizes for:
var computeMaxTextSize = function(data, fontSize, fontName){
var maxH = 0, maxW = 0;
var div = document.createElement('div');
document.body.appendChild(div);
$(div).css({
position: 'absolute',
left: -1000,
top: -1000,
display: 'none',
margin:0,
padding:0
});
$(div).css("font", fontSize + 'px '+fontName);
data.forEach(function(d) {
$(div).html(d);
maxH = Math.max(maxH, $(div).outerHeight());
maxW = Math.max(maxW, $(div).outerWidth());
});
$(div).remove();
return {maxH: maxH, maxW: maxW};
}
Now I will recursively build an array with an array of strings per level:
var allStrings = [[]];
var childStrings = function(level, n) {
var a = allStrings[level];
a.push(n.name);
if(n.children && n.children.length > 0) {
if(!allStrings[level+1]) {
allStrings[level+1] = [];
}
n.children.forEach(function(d) {
childStrings(level + 1, d);
});
}
};
childStrings(0, root);
And then compute the maximum text length per level.
var maxLevelSizes = [];
allStrings.forEach(function(d, i) {
maxLevelSizes.push(computeMaxTextSize(allStrings[i], '10', 'sans-serif'));
});
Then I compute the total text width for all the levels (adding spacing for the little circle icons and some padding to make it look nice). This will be the radius of the final layout. Note that I will use this same padding amount again later on.
var padding = 25; // Width of the blue circle plus some spacing
var totalRadius = d3.sum(maxLevelSizes, function(d) { return d.maxW + padding});
var diameter = totalRadius * 2; // was 960;
var tree = d3.layout.tree()
.size([360, totalRadius])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
Now we can call the layout as usual. There is one last piece: to figure out the radius for the different levels we will need a cumulative sum of the radii of the previous levels. Once we have that we simply assign the new radii to the computed nodes.
// Compute cummulative sums - these will be the ring radii
var newDepths = maxLevelSizes.reduce(function(prev, curr, index) {
prev.push(prev[index] + curr.maxW + padding);
return prev;
},[0]);
var nodes = tree.nodes(root);
// Assign new radius based on depth
nodes.forEach(function(d) {
d.y = newDepths[d.depth];
});
Eh voila! This is maybe not the cleanest solution, and perhaps does not address every concern, but it should get you started. Have fun!

how to implement gesture-based menu

I am supposed to implement a gesture-based menu in which you scroll through a horizontal list of items by panning or flinging through them. This kind of menus is very common in smart phone games. Example case would be Cut the Rope where you select box (Cardboard box, Fabric box) or Angry Birds where you select the set of levels (Poached Eggs, Mighty Hoax).
What I am thinking is that I'll have to do some complex physics calculations and give velocities and accelerations to menu items based on the gestures. Any better solutions? I am using libgdx btw.
I don't think you'd need to go through all that to implement a simple menu! It's all about defining offsets for various items (I'll just assume you want Cut the Rope-style menus, with only one entry in sight at a given moment (excluding transitions)) and then tweening between those offsets whenever a flick is detected!
You seem to have the gesture system all wired up, so right now, we just need to figure out how to display the menu. For simplicity's sake, we'll just assume that we don't want the menu to wrap around.
We'll start by envisioning what this menu will look like, in our heads. It would be just like a filmstrip which passes through the phone and can be seen through the screen.
phone ("stuff" is currently selected)
========
|---------------| |-------|
| Start | About | Stuff | Quit |
|---------------| |-------|
| |
| |
| |
========
We'll just assume that the screen width is w and, consequently, all menu entries are exactly that width (think Cut the Rope again!).
Now, when "Start", is to be displayed, we should just render the flimstrip on the screen starting with the first element, "Start", while the rest would, theoretically, lie to the right of the screen. This will be considered the basic case, rendering the menu with the offset = 0.
Yes, yes, this offset will be the key to our little slidey-slidey menu! Now, it's pretty obvious that when about is selected, we'll just have to offset the "filmstrip" to the left by one "frame", and here offset = - 1 * frameWidth. Our example case illustrated by my brilliant ASCII art has the third menu item selected, and since the frames are indexed starting from 0, we'll just subtract two times the frameWidth and get the desired offset. We'll just render the menu starting at offset = -2 * frameWidth.
(Obviously you can just compute frameWidth in advance, by using the API to fetch the screen width, and then just drawing the menu element text/ graphic centered).
So this is pretty simple:
the user sweeps to the left, we need to get to the menu closer to offset 0, we reduce the index of the selected entity by one and the menu then jumps to the right position
the user sweeps to the right, we increase the index (obviously as long as it doesn't go over the number of menu elements - 1)
But what about smooth tweens?
Libgdx thankfully has interpolations all set for nice little tweens. We just need to take care of a few things so we don't shoot ourselves in the leg. I'll list them here.
One quick note:
The Cut the Rope level selector works a tad differently than what I'm saying here. It doesn't just react to flicks (pre-defined gestures), rather it's more sensitive. You can probably achieve a similar effect by playing with offsets and tracking the position of the finger on the screen. (If the user dragged a menu entry too much to the left/right, transition to the previous/next automatically) Friendly advice: just set up a simple, working menu, and leave details like this towards the end, since they can end up taking a lot of time! :P
Alright, back on track!
What we have now is a way to quickly switch between offsets. We just need to tween. There are some additional members that come into play, but I think they're pretty self-explanatory. While we're transitioning between two elements, we remember the "old" offset, and the one we're heading towards, as well as remembering the time we have left from the transition, and we use these four variables to compute the offset (using a libgdx interpolation, exp10 in this case) at the current moment, resulting in a smooth animation.
Let's see, I've created a quick'n'dirty mock-up. I've commented the code as best as I could, so I hope the following snippet speaks for itself! :D
import java.util.ArrayList;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Interpolation;
public class MenuManager {
// The list of the entries being iterated over
private ArrayList<MenuEntry> entries = new ArrayList<>();
// The current selected thingy
private int index;
// The menu offset
private float offset = 0.0f;
// Offset of the old menu position, before it started tweening to the new one
private float oldOffset = 0.0f;
// What we're tweening towards
private float targetOffset = 0.0f;
// Hardcoded, I know, you can set this in a smarter fashion to suit your
// needs - it's basically as wide as the screen in my case
private float entryWidth = 400.0f;
// Whether we've finished tweening
private boolean finished = true;
// How much time a single transition should take
private float transitionTimeTotal = 0.33f;
// How much time we have left from the current transition
private float transitionTimeLeft = 0.0f;
// libgdx helper to nicely interpolate between the current and the next
// positions
private Interpolation interpolation = Interpolation.exp10;
public void addEntry(MenuEntry entry) {
entries.add(entry);
}
// Called to initiate transition to the next element in the menu
public void selectNext() {
// Don't do anything if we're still animationg
if(!finished) return;
if(index < entries.size() - 1) {
index++;
// We need to head towards the next "frame" of the "filmstrip"
targetOffset = oldOffset + entryWidth;
finished = false;
transitionTimeLeft = transitionTimeTotal;
} else {
// (does nothing now, menu doesn't wrap around)
System.out.println("Cannot go to menu entry > entries.size()!");
}
}
// see selectNext()
public void selectPrevious() {
if(!finished) return;
if(index > 0) {
index --;
targetOffset = oldOffset - entryWidth;
finished = false;
transitionTimeLeft = transitionTimeTotal;
} else {
System.out.println("Cannot go to menu entry <0!");
}
}
// Called when the user selects someting (taps the menu, presses a button, whatever)
public void selectCurrent() {
if(!finished) {
System.out.println("Still moving, hold yer pants!");
} else {
entries.get(index).select();
}
}
public void update(float delta) {
if(transitionTimeLeft > 0.0f) {
// if we're still transitioning
transitionTimeLeft -= delta;
offset = interpolation.apply(oldOffset, targetOffset, 1 - transitionTimeLeft / transitionTimeTotal);
} else {
// Transition is over but we haven't handled it yet
if(!finished) {
transitionTimeLeft = 0.0f;
finished = true;
oldOffset = targetOffset;
}
}
}
// Todo make font belong to menu
public void draw(SpriteBatch spriteBatch, BitmapFont font) {
if(!finished) {
// We're animating, just iterate through everything and draw it,
// it's not like we're wasting *too* much CPU power
for(int i = 0; i < entries.size(); i++) {
entries.get(i).draw((int)(i * entryWidth - offset), 100, spriteBatch, font);
}
} else {
// We're not animating, just draw the active thingy
entries.get(index).draw(0, 100, spriteBatch, font);
}
}
}
And I believe a simple text-based menu entry that can draw itself would suffice! (do mind the dirty hard-coded text-wrap width!)
public class MenuEntry {
private String label;
// private RenderNode2D graphic;
private Action action;
public MenuEntry(String label, Action action) {
this.label = label;
this.action = action;
}
public void select() {
this.action.execute();
}
public void draw(int x, int y, SpriteBatch spriteBatch, BitmapFont font) {
font.drawMultiLine(spriteBatch, label, x, y, 400, HAlignment.CENTER);
}
}
Oh, and Action is just a thingy that has an execute method and, well, represents an action.
public interface Action {
abstract void execute();
}
Feel free to ask any related question in the comments, and I'll try to clarify what's needed.
Hope this helps!

how to fill the DIV container with elements based on container's actual size?

I have a DIV container somewhere on the page with min-width and min-height set to some values.
I am loading small panels (DIVs with display:inline-block) into it using callbacks and javascript.
Right now the number of panels is fixed, so that when the user maximizes the browser window, the size of the container increases, and white space appears at the end of last line.
I want to catch the container's size changes, and load more elements, as many as will fit without clipping into the container's new space.
I saw this on amazon.com. They have panels that display 4 products, but if the browser window is maximized, same panels will display more products filling all the available space in the container.
Scroll-bars must not appear, and no clipping of elements must be done.
Is there a script I could use, or sample code?
Thank you,
Andrei
Found a very nice solution to the problem.
Here's the code: (term 'display' here is used in the same sense as at the supermarket):
function updateDisplay() {
$(".s2", this).hide();
var x = $(".s2", this);
var prevPos = -1;
var nrows = 2;
for (var i = 0; i < x.length; i++) {
$(x[i]).toggle();
var curPos = $(x[i]).position().left;
if (curPos < prevPos) {
if (--nrows == 0) {
$(x[i]).toggle();
break;
}
}
prevPos = curPos;
}
}
function updateDisplays() {
$(".dcon").each(updateDisplay);
}
$(window).resize(updateDisplays);
$(document).ready(updateDisplays);
You can see it in action on the following pages:
www.megabit-mich.ru
www.sportolimpia.ru

Resources