зеркало из https://github.com/mozilla/hubs.git
Add a bunch of killer animations
This commit is contained in:
Родитель
ba473ce5d9
Коммит
09d9095219
|
@ -659,6 +659,11 @@
|
|||
"resolved": "https://registry.npmjs.org/an-array/-/an-array-1.0.0.tgz",
|
||||
"integrity": "sha1-wSWlu4JXd4419LT2qpx9D6nkJmU="
|
||||
},
|
||||
"animejs": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/animejs/-/animejs-2.2.0.tgz",
|
||||
"integrity": "sha1-Ne79/FNbgZScnLBvCz5gwC5v3IA="
|
||||
},
|
||||
"ansi-colors": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.0.5.tgz",
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"aframe-rounded": "^1.0.3",
|
||||
"aframe-slice9-component": "^1.0.0",
|
||||
"aframe-teleport-controls": "github:mozillareality/aframe-teleport-controls#hubs/master",
|
||||
"animejs": "^2.2.0",
|
||||
"classnames": "^2.2.5",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
"deepmerge": "^2.1.1",
|
||||
|
|
|
@ -0,0 +1,643 @@
|
|||
// Taken from A-Frame 0.9.0 master, TODO remove
|
||||
|
||||
const anime = require("animejs");
|
||||
const components = AFRAME.components;
|
||||
const registerComponent = AFRAME.registerComponent;
|
||||
const utils = AFRAME.utils;
|
||||
|
||||
const colorHelperFrom = new THREE.Color();
|
||||
const colorHelperTo = new THREE.Color();
|
||||
|
||||
const getComponentProperty = utils.entity.getComponentProperty;
|
||||
const setComponentProperty = utils.entity.setComponentProperty;
|
||||
const splitCache = {};
|
||||
|
||||
const TYPE_COLOR = "color";
|
||||
const PROP_POSITION = "position";
|
||||
const PROP_ROTATION = "rotation";
|
||||
const PROP_SCALE = "scale";
|
||||
const STRING_COMPONENTS = "components";
|
||||
const STRING_OBJECT3D = "object3D";
|
||||
|
||||
/**
|
||||
* Given property name, check schema to see what type we are animating.
|
||||
* We just care whether the property is a vector.
|
||||
*/
|
||||
function getPropertyType(el, property) {
|
||||
const split = property.split(".");
|
||||
const componentName = split[0];
|
||||
const propertyName = split[1];
|
||||
const component = el.components[componentName] || components[componentName];
|
||||
|
||||
// Primitives.
|
||||
if (!component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Dynamic schema. We only care about vectors anyways.
|
||||
if (propertyName && !component.schema[propertyName]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Multi-prop.
|
||||
if (propertyName) {
|
||||
return component.schema[propertyName].type;
|
||||
}
|
||||
|
||||
// Single-prop.
|
||||
return component.schema.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert object to radians.
|
||||
*/
|
||||
function toRadians(obj) {
|
||||
obj.x = THREE.Math.degToRad(obj.x);
|
||||
obj.y = THREE.Math.degToRad(obj.y);
|
||||
obj.z = THREE.Math.degToRad(obj.z);
|
||||
}
|
||||
|
||||
function addEventListeners(el, eventNames, handler) {
|
||||
let i;
|
||||
for (i = 0; i < eventNames.length; i++) {
|
||||
el.addEventListener(eventNames[i], handler);
|
||||
}
|
||||
}
|
||||
|
||||
function removeEventListeners(el, eventNames, handler) {
|
||||
let i;
|
||||
for (i = 0; i < eventNames.length; i++) {
|
||||
el.removeEventListener(eventNames[i], handler);
|
||||
}
|
||||
}
|
||||
|
||||
function splitDot(path) {
|
||||
if (path in splitCache) {
|
||||
return splitCache[path];
|
||||
}
|
||||
splitCache[path] = path.split(".");
|
||||
return splitCache[path];
|
||||
}
|
||||
|
||||
function getRawProperty(el, path) {
|
||||
let i;
|
||||
let value;
|
||||
const split = splitDot(path);
|
||||
value = el;
|
||||
for (i = 0; i < split.length; i++) {
|
||||
value = value[split[i]];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function setRawProperty(el, path, value, type) {
|
||||
let i;
|
||||
|
||||
if (path.startsWith("object3D.rotation")) {
|
||||
value = THREE.Math.degToRad(value);
|
||||
}
|
||||
|
||||
// Walk.
|
||||
const split = splitDot(path);
|
||||
let targetValue = el;
|
||||
for (i = 0; i < split.length - 1; i++) {
|
||||
targetValue = targetValue[split[i]];
|
||||
}
|
||||
const propertyName = split[split.length - 1];
|
||||
|
||||
// Raw color.
|
||||
if (type === TYPE_COLOR) {
|
||||
if ("r" in targetValue[propertyName]) {
|
||||
targetValue[propertyName].r = value.r;
|
||||
targetValue[propertyName].g = value.g;
|
||||
targetValue[propertyName].b = value.b;
|
||||
} else {
|
||||
targetValue[propertyName].x = value.r;
|
||||
targetValue[propertyName].y = value.g;
|
||||
targetValue[propertyName].z = value.b;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
targetValue[propertyName] = value;
|
||||
}
|
||||
|
||||
function isRawProperty(data) {
|
||||
return data.isRawProperty || data.property.startsWith(STRING_COMPONENTS) || data.property.startsWith(STRING_OBJECT3D);
|
||||
}
|
||||
/**
|
||||
* Animation component for A-Frame using anime.js.
|
||||
*
|
||||
* The component manually controls the tick by setting `autoplay: false` on anime.js and
|
||||
* manually * calling `animation.tick()` in the tick handler. To pause or resume, we toggle a
|
||||
* boolean * flag * `isAnimationPlaying`.
|
||||
*
|
||||
* anime.js animation config for tweenining Javascript objects and values works as:
|
||||
*
|
||||
* config = {
|
||||
* targets: {foo: 0.0, bar: '#000'},
|
||||
* foo: 1.0,
|
||||
* bar: '#FFF'
|
||||
* }
|
||||
*
|
||||
* The above will tween each property in `targets`. The `to` values are set in the root of
|
||||
* the config.
|
||||
*
|
||||
* @member {object} animation - anime.js instance.
|
||||
* @member {boolean} animationIsPlaying - Control if animation is playing.
|
||||
*/
|
||||
module.exports.Component = registerComponent("animation", {
|
||||
schema: {
|
||||
autoplay: { default: true },
|
||||
delay: { default: 0 },
|
||||
dir: { default: "" },
|
||||
dur: { default: 1000 },
|
||||
easing: { default: "easeInQuad" },
|
||||
elasticity: { default: 400 },
|
||||
enabled: { default: true },
|
||||
from: { default: "" },
|
||||
loop: {
|
||||
default: 0,
|
||||
parse: function(value) {
|
||||
// Boolean or integer.
|
||||
if (value === true || value === "true") {
|
||||
return true;
|
||||
}
|
||||
if (value === false || value === "false") {
|
||||
return false;
|
||||
}
|
||||
return parseInt(value, 10);
|
||||
}
|
||||
},
|
||||
property: { default: "" },
|
||||
startEvents: { type: "array" },
|
||||
pauseEvents: { type: "array" },
|
||||
resumeEvents: { type: "array" },
|
||||
round: { default: false },
|
||||
to: { default: "" },
|
||||
type: { default: "" },
|
||||
isRawProperty: { default: false }
|
||||
},
|
||||
|
||||
multiple: true,
|
||||
|
||||
init: function() {
|
||||
const self = this;
|
||||
|
||||
this.eventDetail = { name: this.attrName };
|
||||
this.time = 0;
|
||||
|
||||
this.animation = null;
|
||||
this.animationIsPlaying = false;
|
||||
this.onStartEvent = this.onStartEvent.bind(this);
|
||||
this.beginAnimation = this.beginAnimation.bind(this);
|
||||
this.pauseAnimation = this.pauseAnimation.bind(this);
|
||||
this.resumeAnimation = this.resumeAnimation.bind(this);
|
||||
|
||||
this.fromColor = {};
|
||||
this.toColor = {};
|
||||
this.targets = {};
|
||||
this.targetsArray = [];
|
||||
|
||||
this.updateConfigForDefault = this.updateConfigForDefault.bind(this);
|
||||
this.updateConfigForRawColor = this.updateConfigForRawColor.bind(this);
|
||||
|
||||
this.config = {
|
||||
complete: function() {
|
||||
self.animationIsPlaying = false;
|
||||
self.el.emit("animationcomplete", self.eventDetail, false);
|
||||
if (self.id) {
|
||||
self.el.emit("animationcomplete__" + self.id, self.eventDetail, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
update: function(oldData) {
|
||||
const config = this.config;
|
||||
const data = this.data;
|
||||
|
||||
this.animationIsPlaying = false;
|
||||
|
||||
if (oldData.enabled && !this.data.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.property) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Base config.
|
||||
config.autoplay = false;
|
||||
config.direction = data.dir;
|
||||
config.duration = data.dur;
|
||||
config.easing = data.easing;
|
||||
config.elasticity = data.elasticity;
|
||||
config.loop = data.loop;
|
||||
config.round = data.round;
|
||||
|
||||
// Start new animation.
|
||||
this.createAndStartAnimation();
|
||||
},
|
||||
|
||||
tick: function(t, dt) {
|
||||
if (!this.animationIsPlaying) {
|
||||
return;
|
||||
}
|
||||
this.time += dt;
|
||||
this.animation.tick(this.time);
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
this.pauseAnimation();
|
||||
this.removeEventListeners();
|
||||
},
|
||||
|
||||
pause: function() {
|
||||
this.paused = true;
|
||||
this.pausedWasPlaying = true;
|
||||
this.pauseAnimation();
|
||||
this.removeEventListeners();
|
||||
},
|
||||
|
||||
/**
|
||||
* `play` handler only for resuming scene.
|
||||
*/
|
||||
play: function() {
|
||||
if (!this.paused) {
|
||||
return;
|
||||
}
|
||||
this.paused = false;
|
||||
this.addEventListeners();
|
||||
if (this.pausedWasPlaying) {
|
||||
this.resumeAnimation();
|
||||
this.pausedWasPlaying = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Start animation from scratch.
|
||||
*/
|
||||
createAndStartAnimation: function() {
|
||||
const data = this.data;
|
||||
|
||||
this.updateConfig();
|
||||
this.animationIsPlaying = false;
|
||||
this.animation = anime(this.config);
|
||||
|
||||
this.removeEventListeners();
|
||||
this.addEventListeners();
|
||||
|
||||
// Wait for start events for animation.
|
||||
if (!data.autoplay || (data.startEvents && data.startEvents.length)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delay animation.
|
||||
if (data.delay) {
|
||||
setTimeout(this.beginAnimation, data.delay);
|
||||
return;
|
||||
}
|
||||
|
||||
// Play animation.
|
||||
this.beginAnimation();
|
||||
},
|
||||
|
||||
/**
|
||||
* This is before animation start (including from startEvents).
|
||||
* Set to initial state (config.from, time = 0, seekTime = 0).
|
||||
*/
|
||||
beginAnimation: function() {
|
||||
this.updateConfig();
|
||||
this.time = 0;
|
||||
this.animationIsPlaying = true;
|
||||
this.stopRelatedAnimations();
|
||||
this.el.emit("animationbegin", this.eventDetail);
|
||||
},
|
||||
|
||||
pauseAnimation: function() {
|
||||
this.animationIsPlaying = false;
|
||||
},
|
||||
|
||||
resumeAnimation: function() {
|
||||
this.animationIsPlaying = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* startEvents callback.
|
||||
*/
|
||||
onStartEvent: function() {
|
||||
if (!this.data.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateConfig();
|
||||
if (this.animation) {
|
||||
this.animation.pause();
|
||||
}
|
||||
this.animation = anime(this.config);
|
||||
|
||||
// Include the delay before each start event.
|
||||
if (this.data.delay) {
|
||||
setTimeout(this.beginAnimation, this.data.delay);
|
||||
return;
|
||||
}
|
||||
this.beginAnimation();
|
||||
},
|
||||
|
||||
/**
|
||||
* rawProperty: true and type: color;
|
||||
*/
|
||||
updateConfigForRawColor: function() {
|
||||
const config = this.config;
|
||||
const data = this.data;
|
||||
const el = this.el;
|
||||
let from;
|
||||
let key;
|
||||
let to;
|
||||
|
||||
if (this.waitComponentInitRawProperty(this.updateConfigForRawColor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
from = data.from === "" ? getRawProperty(el, data.property) : data.from;
|
||||
to = data.to;
|
||||
|
||||
// Use r/g/b vector for color type.
|
||||
this.setColorConfig(from, to);
|
||||
from = this.fromColor;
|
||||
to = this.toColor;
|
||||
|
||||
this.targetsArray.length = 0;
|
||||
this.targetsArray.push(from);
|
||||
config.targets = this.targetsArray;
|
||||
for (key in to) {
|
||||
config[key] = to[key];
|
||||
}
|
||||
|
||||
config.update = (function() {
|
||||
const lastValue = {};
|
||||
return function(anim) {
|
||||
const value = anim.animatables[0].target;
|
||||
// For animation timeline.
|
||||
if (value.r === lastValue.r && value.g === lastValue.g && value.b === lastValue.b) {
|
||||
return;
|
||||
}
|
||||
|
||||
setRawProperty(el, data.property, value, data.type);
|
||||
};
|
||||
})();
|
||||
},
|
||||
|
||||
/**
|
||||
* Stuff property into generic `property` key.
|
||||
*/
|
||||
updateConfigForDefault: function() {
|
||||
const config = this.config;
|
||||
const data = this.data;
|
||||
const el = this.el;
|
||||
let from;
|
||||
let to;
|
||||
|
||||
if (this.waitComponentInitRawProperty(this.updateConfigForDefault)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.from === "") {
|
||||
// Infer from.
|
||||
from = isRawProperty(data) ? getRawProperty(el, data.property) : getComponentProperty(el, data.property);
|
||||
} else {
|
||||
// Explicit from.
|
||||
from = data.from;
|
||||
}
|
||||
|
||||
to = data.to;
|
||||
|
||||
const isNumber = !isNaN(from || to);
|
||||
if (isNumber) {
|
||||
from = parseFloat(from);
|
||||
to = parseFloat(to);
|
||||
} else {
|
||||
from = from ? from.toString() : from;
|
||||
to = to ? to.toString() : to;
|
||||
}
|
||||
|
||||
// Convert booleans to integer to allow boolean flipping.
|
||||
const isBoolean = data.to === "true" || data.to === "false" || data.to === true || data.to === false;
|
||||
if (isBoolean) {
|
||||
from = data.from === "true" || data.from === true ? 1 : 0;
|
||||
to = data.to === "true" || data.to === true ? 1 : 0;
|
||||
}
|
||||
|
||||
this.targets.aframeProperty = from;
|
||||
config.targets = this.targets;
|
||||
config.aframeProperty = to;
|
||||
config.update = (function() {
|
||||
let lastValue;
|
||||
|
||||
return function(anim) {
|
||||
let value;
|
||||
value = anim.animatables[0].target.aframeProperty;
|
||||
|
||||
// Need to do a last value check for animation timeline since all the tweening
|
||||
// begins simultaenously even if the value has not changed. Also better for perf
|
||||
// anyways.
|
||||
if (value === lastValue) {
|
||||
return;
|
||||
}
|
||||
lastValue = value;
|
||||
|
||||
if (isBoolean) {
|
||||
value = value >= 1;
|
||||
}
|
||||
|
||||
if (isRawProperty(data)) {
|
||||
setRawProperty(el, data.property, value, data.type);
|
||||
} else {
|
||||
setComponentProperty(el, data.property, value);
|
||||
}
|
||||
};
|
||||
})();
|
||||
},
|
||||
|
||||
/**
|
||||
* Extend x/y/z/w onto the config.
|
||||
* Update vector by modifying object3D.
|
||||
*/
|
||||
updateConfigForVector: function() {
|
||||
const config = this.config;
|
||||
const data = this.data;
|
||||
const el = this.el;
|
||||
let key;
|
||||
|
||||
// Parse coordinates.
|
||||
const from =
|
||||
data.from !== ""
|
||||
? utils.coordinates.parse(data.from) // If data.from defined, use that.
|
||||
: getComponentProperty(el, data.property); // If data.from not defined, get on the fly.
|
||||
const to = utils.coordinates.parse(data.to);
|
||||
|
||||
if (data.property === PROP_ROTATION) {
|
||||
toRadians(from);
|
||||
toRadians(to);
|
||||
}
|
||||
|
||||
// Set to and from.
|
||||
this.targetsArray.length = 0;
|
||||
this.targetsArray.push(from);
|
||||
config.targets = this.targetsArray;
|
||||
for (key in to) {
|
||||
config[key] = to[key];
|
||||
}
|
||||
|
||||
// If animating object3D transformation, run more optimized updater.
|
||||
if (data.property === PROP_POSITION || data.property === PROP_ROTATION || data.property === PROP_SCALE) {
|
||||
config.update = (function() {
|
||||
const lastValue = {};
|
||||
return function(anim) {
|
||||
const value = anim.animatables[0].target;
|
||||
|
||||
if (data.property === PROP_SCALE) {
|
||||
value.x = Math.max(0.0001, value.x);
|
||||
value.y = Math.max(0.0001, value.y);
|
||||
value.z = Math.max(0.0001, value.z);
|
||||
}
|
||||
|
||||
// For animation timeline.
|
||||
if (value.x === lastValue.x && value.y === lastValue.y && value.z === lastValue.z) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastValue.x = value.x;
|
||||
lastValue.y = value.y;
|
||||
lastValue.z = value.z;
|
||||
|
||||
el.object3D[data.property].set(value.x, value.y, value.z);
|
||||
};
|
||||
})();
|
||||
return;
|
||||
}
|
||||
|
||||
// Animating some vector.
|
||||
config.update = (function() {
|
||||
const lastValue = {};
|
||||
return function(anim) {
|
||||
const value = anim.animations[0].target;
|
||||
|
||||
// Animate rotation through radians.
|
||||
// For animation timeline.
|
||||
if (value.x === lastValue.x && value.y === lastValue.y && value.z === lastValue.z) {
|
||||
return;
|
||||
}
|
||||
lastValue.x = value.x;
|
||||
lastValue.y = value.y;
|
||||
lastValue.z = value.z;
|
||||
setComponentProperty(el, data.property, value);
|
||||
};
|
||||
})();
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the config before each run.
|
||||
*/
|
||||
updateConfig: function() {
|
||||
// Route config type.
|
||||
const propType = getPropertyType(this.el, this.data.property);
|
||||
if (isRawProperty(this.data) && this.data.type === TYPE_COLOR) {
|
||||
this.updateConfigForRawColor();
|
||||
} else if (propType === "vec2" || propType === "vec3" || propType === "vec4") {
|
||||
this.updateConfigForVector();
|
||||
} else {
|
||||
this.updateConfigForDefault();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Wait for component to initialize.
|
||||
*/
|
||||
waitComponentInitRawProperty: function(cb) {
|
||||
const data = this.data;
|
||||
const el = this.el;
|
||||
const self = this;
|
||||
|
||||
if (data.from !== "") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!data.property.startsWith(STRING_COMPONENTS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const componentName = splitDot(data.property)[1];
|
||||
if (el.components[componentName]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
el.addEventListener("componentinitialized", function wait(evt) {
|
||||
if (evt.detail.name !== componentName) {
|
||||
return;
|
||||
}
|
||||
cb();
|
||||
// Since the config was created async, create the animation now since we missed it
|
||||
// earlier.
|
||||
self.animation = anime(self.config);
|
||||
el.removeEventListener("componentinitialized", wait);
|
||||
});
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Make sure two animations on the same property don't fight each other.
|
||||
* e.g., animation__mouseenter="property: material.opacity"
|
||||
* animation__mouseleave="property: material.opacity"
|
||||
*/
|
||||
stopRelatedAnimations: function() {
|
||||
let component;
|
||||
let componentName;
|
||||
for (componentName in this.el.components) {
|
||||
component = this.el.components[componentName];
|
||||
if (componentName === this.attrName) {
|
||||
continue;
|
||||
}
|
||||
if (component.name !== "animation") {
|
||||
continue;
|
||||
}
|
||||
if (!component.animationIsPlaying) {
|
||||
continue;
|
||||
}
|
||||
if (component.data.property !== this.data.property) {
|
||||
continue;
|
||||
}
|
||||
component.animationIsPlaying = false;
|
||||
}
|
||||
},
|
||||
|
||||
addEventListeners: function() {
|
||||
const data = this.data;
|
||||
const el = this.el;
|
||||
addEventListeners(el, data.startEvents, this.onStartEvent);
|
||||
addEventListeners(el, data.pauseEvents, this.pauseAnimation);
|
||||
addEventListeners(el, data.resumeEvents, this.resumeAnimation);
|
||||
},
|
||||
|
||||
removeEventListeners: function() {
|
||||
const data = this.data;
|
||||
const el = this.el;
|
||||
removeEventListeners(el, data.startEvents, this.onStartEvent);
|
||||
removeEventListeners(el, data.pauseEvents, this.pauseAnimation);
|
||||
removeEventListeners(el, data.resumeEvents, this.resumeAnimation);
|
||||
},
|
||||
|
||||
setColorConfig: function(from, to) {
|
||||
colorHelperFrom.set(from);
|
||||
colorHelperTo.set(to);
|
||||
from = this.fromColor;
|
||||
to = this.toColor;
|
||||
from.r = colorHelperFrom.r;
|
||||
from.g = colorHelperFrom.g;
|
||||
from.b = colorHelperFrom.b;
|
||||
to.r = colorHelperTo.r;
|
||||
to.g = colorHelperTo.g;
|
||||
to.b = colorHelperTo.b;
|
||||
}
|
||||
});
|
|
@ -42,6 +42,27 @@ AFRAME.registerComponent("pinnable", {
|
|||
_fireEvents() {
|
||||
if (this.data.pinned) {
|
||||
this.el.emit("pinned", { el: this.el });
|
||||
|
||||
this.el.removeAttribute("animation__pin-start");
|
||||
this.el.removeAttribute("animation__pin-end");
|
||||
const currentScale = this.el.object3D.scale;
|
||||
|
||||
this.el.setAttribute("animation__pin-start", {
|
||||
property: "scale",
|
||||
dur: 100,
|
||||
from: { x: currentScale.x, y: currentScale.y, z: currentScale.z },
|
||||
to: { x: currentScale.x * 1.1, y: currentScale.y * 1.1, z: currentScale.z * 1.1 },
|
||||
easing: "easeInOutElastic"
|
||||
});
|
||||
|
||||
this.el.setAttribute("animation__pin-end", {
|
||||
property: "scale",
|
||||
delay: 150,
|
||||
dur: 100,
|
||||
from: { x: currentScale.x * 1.1, y: currentScale.y * 1.1, z: currentScale.z * 1.1 },
|
||||
to: { x: currentScale.x, y: currentScale.y, z: currentScale.z },
|
||||
easing: "easeInOutElastic"
|
||||
});
|
||||
} else {
|
||||
this.el.emit("unpinned", { el: this.el });
|
||||
}
|
||||
|
|
|
@ -3,7 +3,18 @@ AFRAME.registerComponent("remove-networked-object-button", {
|
|||
this.onClick = () => {
|
||||
if (!NAF.utils.isMine(this.targetEl) && !NAF.utils.takeOwnership(this.targetEl)) return;
|
||||
|
||||
this.targetEl.parentNode.removeChild(this.targetEl);
|
||||
this.targetEl.setAttribute("animation__remove", {
|
||||
property: "scale",
|
||||
dur: 200,
|
||||
to: { x: 0.01, y: 0.01, z: 0.01 },
|
||||
easing: "easeInQuad"
|
||||
});
|
||||
|
||||
this.el.parentNode.setAttribute("visible", false);
|
||||
|
||||
this.targetEl.addEventListener("animationcomplete", () => {
|
||||
this.targetEl.parentNode.removeChild(this.targetEl);
|
||||
});
|
||||
};
|
||||
|
||||
NAF.utils.getNetworkedEntity(this.el).then(networkedEl => {
|
||||
|
|
|
@ -167,6 +167,7 @@
|
|||
class="interactable"
|
||||
super-networked-interactable="counter: #media-counter;"
|
||||
body="type: dynamic; shape: none; mass: 1;"
|
||||
animation__spawn="property: scale; dur: 200; from: 0.5 0.5 0.5; to: 1 1 1; easing: easeInQuad"
|
||||
grabbable
|
||||
stretchable="useWorldPosition: true; usePhysics: never"
|
||||
hoverable
|
||||
|
@ -239,6 +240,7 @@
|
|||
hoverable
|
||||
stretchable
|
||||
camera-tool
|
||||
animation__spawn="property: scale; dur: 200; from: 0.5 0.5 0.5; to: 1 1 1; easing: easeInQuad"
|
||||
body="type: dynamic; shape: none; mass: 1;"
|
||||
shape="shape: box; halfExtents: 0.22 0.145 0.1; offset: 0 0.02 0"
|
||||
sticky-object="autoLockOnRelease: true; autoLockOnLoad: true; autoLockSpeedLimit: 0;"
|
||||
|
|
|
@ -67,6 +67,7 @@ import "./components/scene-sound";
|
|||
import "./components/emit-state-change";
|
||||
import "./components/action-to-event";
|
||||
import "./components/stop-event-propagation";
|
||||
import "./components/animation";
|
||||
|
||||
import ReactDOM from "react-dom";
|
||||
import React from "react";
|
||||
|
|
|
@ -115,6 +115,15 @@ export const addMedia = (src, template, contentOrigin, resolve = false, resize =
|
|||
["model-loaded", "video-loaded", "image-loaded"].forEach(eventName => {
|
||||
entity.addEventListener(eventName, () => {
|
||||
clearTimeout(fireLoadingTimeout);
|
||||
|
||||
entity.setAttribute("animation__spawn-start", {
|
||||
property: "scale",
|
||||
dur: 300,
|
||||
from: { x: 0.5, y: 0.5, z: 0.5 },
|
||||
to: { x: 1.0, y: 1.0, z: 1.0 },
|
||||
easing: "easeOutElastic"
|
||||
});
|
||||
|
||||
scene.emit("media-loaded", { src: src });
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче