I am having trouble calculating the height and width of slots. I am trying to render images in my Perimeter component. These images have a size 105x160. However, when I console.log the clientWidth and clientHeight, I get 0x24.
I believe my problem is related to this: In vue.js 2, measure the height of a component once slots are rendered but I still can't figure it out. I've tried using $nextTick on both the Perimeter component and the individual slot components.
In my Perimeter component, I have:
<template>
<div class="d-flex">
<slot></slot>
<div class="align-self-center">
<slot name="center-piece"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'Perimeter',
mounted() {
this.distributeSlots();
},
updated() {
this.distributeSlots();
},
computed: {
centerRadius() {
return this.$slots['center-piece'][0].elm.clientWidth / 2;
},
},
methods: {
distributeSlots() {
let angle = 0;
const {
clientHeight: componentHeight,
clientWidth: componentWidth,
offsetTop: componentOffsetTop,
offsetLeft: componentOffsetLeft,
} = this.$el;
const componentXCenter = componentWidth / 2;
const componentYCenter = componentHeight / 2;
const slots = this.$slots.default.filter(slot => slot.tag) || [];
const step = (2 * Math.PI) / slots.length;
slots.forEach((slot) => {
slot.context.$nextTick(() => {
const { height, width } = slot.elm.getBoundingClientRect();
console.log(`height ${height}, width ${width}`);
const distanceFromCenterX = (this.centerRadius + componentXCenter) * Math.cos(angle);
const distanceFromCenterY = (this.centerRadius + componentYCenter) * Math.sin(angle);
const x = Math.round((componentXCenter + distanceFromCenterX + componentOffsetLeft) - (width / 2));
const y = Math.round((componentYCenter + distanceFromCenterY + componentOffsetTop) - (height / 2));
slot.elm.style.left = `${x}px`;
slot.elm.style.top = `${y}px`;
angle += step;
});
});
},
},
};
</script>
I also had my distributeSlots() method written without $nextTick:
distributeSlots() {
let angle = 0;
const {
clientHeight: componentHeight,
clientWidth: componentWidth,
offsetTop: componentOffsetTop,
offsetLeft: componentOffsetLeft,
} = this.$el;
const componentXCenter = componentWidth / 2;
const componentYCenter = componentHeight / 2;
const slots = this.$slots.default.filter(slot => slot.tag) || [];
const step = (2 * Math.PI) / slots.length;
slots.forEach((slot) => {
const { height, width } = slot.elm.getBoundingClientRect();
const distanceFromCenterX = (this.centerRadius + componentXCenter) * Math.cos(angle);
const distanceFromCenterY = (this.centerRadius + componentYCenter) * Math.sin(angle);
const x = Math.round((componentXCenter + distanceFromCenterX + componentOffsetLeft) - (width / 2));
const y = Math.round((componentYCenter + distanceFromCenterY + componentOffsetTop) - (height / 2));
slot.elm.style.left = `${x}px`;
slot.elm.style.top = `${y}px`;
angle += step;
});
},
I am passing to the Perimeter component as follows:
<template>
<perimeter>
<div v-for="(book, index) in books.slice(0, 6)" v-if="book.image" :key="book.asin" style="position: absolute">
<router-link :to="{ name: 'books', params: { isbn: book.isbn }}">
<img :src="book.image" />
</router-link>
</div>
<perimeter>
</template>
Even worse, when I console.log(slot.elm) in the forEach function and open up the array in the browser console, I see the correct clientHeight + clientWidth:
Usually in such cases it's a logical mistake, rather than an issue with the framework. So I would go with simplifying your code to the bare minimum that demonstrates your issue.
Assuming you get clientWidth and clientHeight on mounted() or afterwards, as demonstarted below, it should just work.
Avoid any timer hacks, they are the culprit for bugs that are extremely hard to debug.
<template>
<div style="min-height: 100px; min-width: 100px;">
<slot />
</div>
</template>
<script>
export default {
name: 'MyContainer',
data (){
return {
width: 0,
height: 0,
}
},
mounted (){
this.width = this.$slots["default"][0].elm.clientWidth
this.height = this.$slots["default"][0].elm.clientHeight
console.log(this.width, this.height) // => 100 100 (or more)
},
}
</script>
<style scoped lang="scss">
</style>
You can use a trick and put the code in a setTimeout method to execute it in another thread with a tiny delay:
setTimeout(() => {
// Put your code here
...
}, 80)
Related
I'm working on a small flappy-bird-like-game demo. Everthing seems fine, but I have a small problem/question.
I setup a collider function, and the callback works as expected, when the "two" objects collide, but there is a strange behavior:
the white-square (the bird) can fly through the obstacles, when coming from the side
but cannot passthrough when coming from below or on above
BUT the callback is execute always.
blue arrow marks where the square passes through
green arrows mark where the square doesn't passthrough
I'm not sure if this is, because I'm using rectangles (they sometimes) cause problems, or because of my nested physics setup. I even tried to replaced the white rectangel with a sprite, but this still produces the same result/error.
For my demo: I could probablly just destroy the object and restart the level on collision, but I still would like to understand why this is happening? And how I can prevent this, inconsistant behavior.
I'm probably missing something, but couldn't find it, and I don't want to rebuild the application again.
So my question is: why is this happening? And How can I prevent this?
Here is the code:
const width = 400;
const height = 200;
const spacing = width / 4;
const levelSpeed = width / 4;
const flySpeed = width / 4;
var GameScene = {
create (){
let player = this.add.rectangle(width / 4, height / 2, 20, 20, 0xffffff);
this.physics.add.existing(player);
this.input.keyboard.on('keydown-SPACE', (event) => {
if(player.body.velocity.y >= -flySpeed/2){
player.body.setVelocityY(-flySpeed);
}
});
player.body.onWorldBounds = true;
player.body.setCollideWorldBounds(true );
this.physics.world.on("worldbounds", function (body) {
console.info('GAME OVER');
player.y = height / 2;
player.body.setVelocity(0);
});
this.pipes = [];
for(let idx = 1; idx <= 10; idx++) {
let obstacle = this.createObstacle(spacing * idx, Phaser.Math.Between(-height/3, 0));
this.add.existing(obstacle);
this.pipes.push(obstacle);
this.physics.add.collider(obstacle.list[0], player)
this.physics.add.collider(obstacle.list[1], player, _ => console.info(2))
}
},
update(){
this.pipes.forEach((item) => {
if(item.x <= 0){
item.body.x = spacing * 10;
}
})
},
extend: {
createObstacle (x, y){
let topPipe = (new Phaser.GameObjects.Rectangle(this, 0, 0 , 20 , height / 2 ,0xff0000)).setOrigin(0);
let bottomPipe = (new Phaser.GameObjects.Rectangle(this, 0, height/2 + 75, 20 , height / 2 ,0xff0000)).setOrigin(0);
this.physics.add.existing(topPipe);
this.physics.add.existing(bottomPipe);
topPipe.body.setImmovable(true);
topPipe.body.allowGravity = false;
bottomPipe.body.setImmovable(true);
bottomPipe.body.allowGravity = false;
let obstacle = new Phaser.GameObjects.Container(this, x, y, [
topPipe,
bottomPipe
]);
this.physics.add.existing(obstacle);
obstacle.body.velocity.x = - levelSpeed;
obstacle.body.allowGravity = false;
return obstacle;
}
}
};
var config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width,
height,
scene: [GameScene],
physics: {
default: 'arcade',
arcade: {
gravity: { y: flySpeed },
debug: true
},
}
};
var game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Currently I just can assume, that the physics objects don't seem to work correct, when physics objects are nested.
Maybe I'm wrong, but since I rewrote the code again without nested physics - objects and it seems to work, I think my assumption Is correct. I shouldn't have tried to over engineer my code.
If someone has more insides, please let me know/share. I still not 100% sure, if this is the real reason, for the strange behavior.
Here the rewriten code:
const width = 400;
const height = 200;
const spacing = width / 4;
const levelSpeed = width / 4;
const flySpeed = width / 4;
var GameScene = {
create (){
let player = this.add.rectangle(width / 4, height / 2, 20, 20, 0xffffff);
this.physics.add.existing(player);
this.input.keyboard.on('keydown-SPACE', (event) => {
if(player.body.velocity.y >= -flySpeed/2){
player.body.setVelocityY(-flySpeed);
}
});
player.body.onWorldBounds = true;
player.body.setCollideWorldBounds(true );
this.physics.world.on("worldbounds", function (body) {
console.info('GAME OVER');
player.x = width / 4;
player.y = height / 2;
player.body.setVelocity(0);
});
this.pipes = [];
for(let idx = 1; idx <= 10; idx++) {
let obstacle = this.createObstacle(spacing * idx, Phaser.Math.Between(-height/3, 0));
this.add.existing(obstacle[0]);
this.add.existing(obstacle[1]);
this.pipes.push(...obstacle);
this.physics.add.collider(obstacle[0], player)
this.physics.add.collider(obstacle[1], player, _ => console.info(2))
}
},
update(){
this.pipes.forEach((item) => {
if(item.x <= 0){
item.body.x = spacing * 10;
}
item.body.velocity.x = - levelSpeed;
})
},
extend: {
createObstacle (x, y){
let topPipe = (new Phaser.GameObjects.Rectangle(this, x, -20 , 20 , height / 2 ,0xff0000)).setOrigin(0);
let bottomPipe = (new Phaser.GameObjects.Rectangle(this, x, height/2 + 75, 20 , height / 2 ,0xff0000)).setOrigin(0);
this.physics.add.existing(topPipe);
this.physics.add.existing(bottomPipe);
topPipe.body.setImmovable(true);
topPipe.body.allowGravity = false;
topPipe.body.velocity.x = - levelSpeed;
bottomPipe.body.setImmovable(true);
bottomPipe.body.allowGravity = false;
bottomPipe.body.velocity.x = - levelSpeed;
return [topPipe, bottomPipe];
}
}
};
var config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width,
height,
scene: [GameScene],
physics: {
default: 'arcade',
arcade: {
gravity: { y: flySpeed },
debug: true
},
}
};
var game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Is there a way to achieve the effect of mouse hover like on the website below:
https://studiomaertens.com/about
if you hover on the Work or About link, there is a magnetic effect on it as well as the cursor automatically scales and positions itself in the center with the link,
So far I've managed to combine two different examples together, the magnetic effect works but the cursor doesn't align properly with the icons
https://codepen.io/pen/?template=mdPmPbK
<main>
<div>
<button class="cerchio" >
<ion-icon name="logo-facebook"></ion-icon>
</button>
</div>
<button>
<ion-icon name="logo-twitter"></ion-icon>
</button>
</main>
<div class="cursor cursor--large"></div>
<div class="cursor cursor--small"></div>
var cerchio = document.querySelectorAll('.cerchio');
cerchio.forEach(function(elem){
$(document).on('mousemove touch', function(e){
magnetize(elem, e);
});
})
function magnetize(el, e){
var mX = e.pageX,
mY = e.pageY;
const item = $(el);
const customDist = item.data('dist') * 20 || 120;
const centerX = item.offset().left + (item.width()/2);
const centerY = item.offset().top + (item.height()/2);
var deltaX = Math.floor((centerX - mX)) * -0.45;
var deltaY = Math.floor((centerY - mY)) * -0.45;
var distance = calculateDistance(item, mX, mY);
if(distance < customDist){
TweenMax.to(item, 0.5, {y: deltaY, x: deltaX, scale:1.1});
item.addClass('magnet');
}
else {
TweenMax.to(item, 0.6, {y: 0, x: 0, scale:1});
item.removeClass('magnet');
}
}
function calculateDistance(elem, mouseX, mouseY) {
return Math.floor(Math.sqrt(Math.pow(mouseX - (elem.offset().left+(elem.width()/2)), 2) + Math.pow(mouseY - (elem.offset().top+(elem.height()/2)), 2)));
}
/*- MOUSE STICKY -*/
function lerp(a, b, n) {
return (1 - n) * a + n * b
}
// Inizio Cursor
class Cursor {
constructor() {
this.bind()
//seleziono la classe del cursore
this.cursor = document.querySelector('.js-cursor')
this.mouseCurrent = {
x: 0,
y: 0
}
this.mouseLast = {
x: this.mouseCurrent.x,
y: this.mouseCurrent.y
}
this.rAF = undefined
}
bind() {
['getMousePosition', 'run'].forEach((fn) => this[fn] = this[fn].bind(this))
}
getMousePosition(e) {
this.mouseCurrent = {
x: e.clientX,
y: e.clientY
}
}
run() {
this.mouseLast.x = lerp(this.mouseLast.x, this.mouseCurrent.x, 0.2)
this.mouseLast.y = lerp(this.mouseLast.y, this.mouseCurrent.y, 0.2)
this.mouseLast.x = Math.floor(this.mouseLast.x * 100) / 100
this.mouseLast.y = Math.floor(this.mouseLast.y * 100) / 100
this.cursor.style.transform = `translate3d(${this.mouseLast.x}px, ${this.mouseLast.y}px, 0)`
this.rAF = requestAnimationFrame(this.run)
}
requestAnimationFrame() {
this.rAF = requestAnimationFrame(this.run)
}
addEvents() {
window.addEventListener('mousemove', this.getMousePosition, false)
}
on() {
this.addEvents()
this.requestAnimationFrame()
}
init() {
this.on()
}
}
const cursor = new Cursor()
cursor.init();
Might there be a way to make a similar effect on the Studiomaertens website above?
Kindly let me know
Thank you
To align the cursor in center use
.cursor{
transform: translate(-50%,-50%);
}
So I am building a video game where some fireball drop down the screen. However, there is only one image crossing the screen at a time. I would like that the image is actually multiplied an number of times. To get an idea of what I am saying, here is an image:
But what I would like to do is instead of only having one image (fireball) going down the screen, I would like to have a bunch of images dropping down the screen.
Here is the code for the fireball:
//Fireball script
function fFireball(offset) {
return Math.floor(Math.random() * (window.innerWidth - offset))
}
let fireball = {x: fFireball(fireballElement.offsetWidth), y: 0}
const fireLoop = function() {
fireball.y += 2; fireballElement.style.top = fireball.y + 'px'
if (fireball.y > window.innerHeight) {
fireball.x = fFireball(fireballElement.offsetWidth)
fireballElement.style.left = fireball.x + 'px'; fireball.y = 0
}
}
fireballElement.style.left = fireball.x + 'px'
let fireInterval = setInterval(fireLoop, 1000 / 100)
And the image:
<img src="Photo/fireball.png" id="fireball">
Thanks!
Use document.createElement()
Demo : https://jsfiddle.net/hexzero/ukh1dpwn/
I wrote down several comments in the code below to help you understand it better. If you have any additional question don't hesitate to ask.
const fireballArray = [];
// You can add any additional attributes you need for your fire balls here like ids and class names.
function generateFireBallWithAttributes(el, attrs) {
for (var key in attrs) {
el.setAttribute(key, attrs[key]);
}
return el;
}
function createFireBalls(amount){
for (let i = 0; i <= amount; i++) {
fireballArray.push( // create an image element
generateFireBallWithAttributes(document.createElement("img"), {
src: "Photo/fireball.png",
width: "32",
height: "32",
})
);
}}
createFireBalls(10)
fireballArray.forEach((fireballElement) => {
// Just add the id of the game body here, so that you could append your fire balls
document.getElementById("game-body").appendChild(fireballElement);
const fireball = { x: fFireball(fireballElement.offsetWidth), y: 0 };
const fireLoop = function () {
fireball.y += 2;
fireballElement.style.top = fireball.y + "px";
if (fireball.y > window.innerHeight) {
fireball.x = fFireball(fireballElement.offsetWidth);
fireballElement.style.left = fireball.x + "px";
fireball.y = 0;
}
};
fireballElement.style.left = fireball.x + "px";
// I've randomised the interval
// you may want to implement your own version to get more control
let fireInterval = setInterval(fireLoop, 1000 / ((Math.random() * (125 - 75)) + 75));
});
function fFireball(offset) {
return Math.floor(Math.random() * (window.innerWidth - offset));
}
img {
position: absolute;
}
I got inspired by this post Setting multiple attributes for an element at once with JavaScript, when adding extra attributes. If you like it please show them some love.
You can simply create a new <img> element for each bullet
I created a snippet doing it the way you were doing it. However, it would be more efficient if you had a single loop that updates all of the images at once.
const create = (x, y, vx, vy) => {
const object = {
x,
y,
vx,
vy,
el: document.createElement("img")
}
object.el.src = "http://placehold.it/50x50";
object.el.style.left = `${object.x}px`;
object.el.style.top = `${object.y}px`;
document.body.appendChild(object.el);
const intervalID = setInterval(() => {
object.x += object.vx;
object.y += object.vy;
object.el.style.left = `${object.x}px`;
object.el.style.top = `${object.y}px`;
if (object.y > window.innerHeight) {
document.body.removeChild(object.el);
clearInterval(intervalID)
}
}, 20);
}
setInterval(() => {
const randX = Math.floor(Math.random() * (window.innerWidth - 50));
create(randX, -50, 0, 2);
}, 500);
body {
margin: 0;
overflow: hidden;
}
img {
position: absolute;
}
Hi I am clicking on particular point on image and displaying a icon on selected area. When i change resolution that point is moving to other part of image. below is my code.How to fix the coords on resolution or browser width change. Added eventlistner and need to know on window resize any calculation have to be done?
My component is:
import React, { Component } from "react";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import { showCardDetails } from "../../store/Action";
let temp = [];
class cardDetails extends Component {
constructor(props) {
super(props);
}
state = {
left: "",
right: "",
coords: []
};
componentDidMount() {
const { dispatch } = this.props;
console.log(this.props.backOffice + "props");
console.log(this.props.match.params.userId);
let coords = JSON.parse(localStorage.getItem("coords"));
this.setState({ coords: coords });
window.addEventListener("resize", this.handlePress);
}
handlePress = () => {
const { coords } = this.state;
console.log(this.state);
//anycalculation here?
};
handleclick = e => {
var canvas = document.getElementById("imgCoord");
var w = document.documentElement.clientWidth;
var h = document.documentElement.clientHeight;
console.log(e.offsetLeft);
var x =
e.clientX +
document.body.scrollLeft +
document.documentElement.scrollLeft;
var y =
e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
let left = x + "px";
let top = y + "px";
let obj = {
left: left,
top: top,
width: w,
height: h
};
temp.push(obj);
this.setState({ left: left, top: top, coords: temp });
localStorage.setItem("coords", JSON.stringify(temp));
};
render() {
const { backOffice, match } = this.props;
const { left, top, coords } = this.state;
console.log(coords);
return (
<React.Fragment>
<div>
<img
id="imgCoord"
src={require("../../assets/images/imageTagging.PNG")}
onClick={$event => this.handleclick($event)}
/>
{coords &&
coords.map(item => (
<img
style={{
width: "20px",
position: "absolute",
left: item.left,
top: item.top,
backgroundColor: "white"
}}
src={require("../../assets/images/tag.png")}
id="marker"
/>
))}
</div>
</React.Fragment>
);
}
}
/**
* Redux map state
#param {} state
#param {} ownParams
*/
function mapStateToProps(state, ownParams) {
console.log(state);
return {
backOffice: state.selectedCardData
};
}
export default withRouter(connect(mapStateToProps)(cardDetails));
[EDITED]
you need to add a event listener (here is docs https://www.w3schools.com/jsref/event_onresize.asp ) in componentDidMount method, and remove this listener in componentWillUnmount method.
inside listener put a function to set new coordinates.
and also need to calculate a percentage of icon's coordinates relative to width and height of the window
handleclick = e => {
...your previous code
const yPercent = h / top;
const xPercent = w / left;
this.setState({ yPercent, xPercent });
};
handleResize = () => {
var w = document.documentElement.clientWidth;
var h = document.documentElement.clientHeight;
const newTop = (this.state.yPercent * h) + "px";
const newLeft = (this.state.xPercent * w) + "px";
this.setState({ left: newLeft; top: newTop });
}
I have problems displaying recently uploaded photos to a form. If the image was made by phone, it may have an exif property, but the browser does not handle this property, it turns out that the picture can be flipped over or on the side.
upside down picture
I solved this problem with the exif-js library. Having written (having stolen on some website) a component and having spent a lot of time, I achieved that during loading and processing, the picture turns in the right direction.
right side picture
But during the test, we found out that on iPhones, if you download not from photos, but take photos vertically manually, then the picture generally goes beyond the div to the lower left corner.
iphone incorrectly image
After that all other photos are also displayed incorrectly. On android device all is fine.
In this img-tag: src changes dynamicly and I need 'transform' dynamicly too, so I need to use updated() method. I know this is bad code, but...
Usage:
<div class="form-group">
<div class="row">
<div class="col-md-6 edit-activity-images">
<auto-rotate><img class="edit-activity-images-inner" alt="activity"
:src="getUploadedImageBackground()||staticFile('images/img-place-holder.png')"
></auto-rotate>
Style
edit-activity-images-inner{
object-fit: cover;
width: 100%;
height: 100%;
}
Component
<template>
<div class="holder">
<slot></slot>
</div>
</template>
<script>
import imagesLoaded from 'imagesloaded'
import EXIF from 'exif-js'
const rotateAngles = [
{},
{y: 0, z: 0},
{y: 180, z: 0},
{y: 0, z: 180},
{y: 180, z: 180},
{y: 180, z: -90},
{y: 0, z: -90},
{y: 180, z: 90},
{y: 0, z: 90}
];
function getOrientation(el) {
return new Promise(function (resolve) {
imagesLoaded(el, function () {
EXIF.getData(el, function () {
const orientation = EXIF.getTag(this, 'Orientation');
resolve(orientation);
})
})
})
}
function getRotationAngle(newOrientation, oldOrientation) {
const y = rotateAngles[newOrientation].y - rotateAngles[oldOrientation].y;
let z = rotateAngles[newOrientation].z - rotateAngles[oldOrientation].z;
if (y)
z = z * -1;
return { y, z }
}
const recently_added = {};
const havent_exif = {};
export default {
name: "AutoRotate",
updated() {
const slot = this.$slots.default[0];
if (slot.tag !== 'img') {
console.error('Warning: slot should be an img tag.');
return;
}
const holder = this.$el;
const img = holder.childNodes[0];
img.exifdata = undefined;
if (recently_added[img.src]) {
img.style['transform'] = recently_added[img.src];
return
} else if (havent_exif[img.src]) {
img.style['transform'] = "translate(0px, 0px) rotateY(0deg) rotateZ(0deg)";
return;
}
getOrientation(img).then(function (currentOrientation) {
if (currentOrientation !== undefined) {
const angle = getRotationAngle(1, currentOrientation);
const transform = `rotateY(${angle.y}deg) rotateZ(${angle.z}deg)`;
let translate = '';
if (angle.z % 180 !== 0) {
const height = img.clientHeight;
const width = img.clientWidth;
translate = `translate(${(height - width) / 2}px, ${(width - height) / 2}px)`;
holder.style['width'] = `${height}px`;
holder.style['height'] = `${width}px`;
}
img.style['vertical-align'] = 'bottom';
img.style['transform'] = translate + ' ' + transform;
recently_added[img.src] = translate + ' ' + transform;
} else {
havent_exif[img.src] = true;
if (!recently_added[img.src]) {
img.style['transform'] = "translate(0px, 0px) rotateY(0deg) rotateZ(0deg)";
}
}
})
}
}
<style scoped>
.holder {
object-fit: cover;
width: 100%;
height: 100%;
}
</style>
Well, hope someone can help me and write what can I do...
Well, I removed this part of code, because it works incorrect. And it's done...
if (angle.z % 180 !== 0) {
const height = img.clientHeight;
const width = img.clientWidth;
translate = `translate(${(height - width) / 2}px, ${(width - height) / 2}px)`;
holder.style['width'] = `${height}px`;
holder.style['height'] = `${width}px`;
}