So I've been trying to replicate the tile chebyshev lab outside of the phaser lab area, in glitch, and I've been Using the variable solution I got in this answer about using Phaser.gamestate for my variable that need to be update in many locations
And my Console hits me with Cannot read properties of undefined (reading 'gameState')
I've tried the this context setting too, but it continues this error, what can I do to fix it?
My Code:
import Phaser from "../lib/phaser.js";
export default class Game extends Phaser.Scene {
constructor() {
super("game1");
}
preload() {
this.load.image(
"Tileset",
"https://cdn.glitch.global/cc90578e-c3d0-47c5-bb0d-f5a81263b5b6/pixil-frame-0%20(17).png?v=1675985219390"
);
this.load.tilemapTiledJSON(
"map",
"https://cdn.glitch.global/cc90578e-c3d0-47c5-bb0d-f5a81263b5b6/Map.tmj?v=1675985261369"
);
this.load.spritesheet(
"player",
"https://cdn.glitch.global/cc90578e-c3d0-47c5-bb0d-f5a81263b5b6/pixil-frame-0%20(13).png?v=1675904194091",
{ frameWidth: 32, frameHeight: 32 }
);
}
create() {
this.gameState = {
map: "",
cursors: "",
};
this.gameState.map = this.make.tilemap({ key: "map" });
this.tiles = this.gameState.map.addTilesetImage("tileSet", "Tileset");
this.worldLayer = this.gameState.map.createLayer(
"Block Layer",
this.tiles,
0,
0
);
this.player = this.physics.add.sprite(0, 0, "player", 1);
this.cameras.main.setBounds(
0,
0,
this.gameState.map.widthInPixels,
this.gameState.map.heightInPixels
);
this.cameras.main.startFollow(this.player);
this.gameState.cursors = this.input.keyboard.createCursorKeys();
}
update() {
function updateMap() {
console.log(this.gameState)
var origin = this.gameState.map.getTileAtWorldXY(
this.player.x,
this.player.y
);
this.gameState.map.forEachTile(function (tile) {
var dist = Phaser.Math.Distance.Chebyshev(
origin.x,
origin.y,
tile.x,
tile.y
);
tile.setAlpha(1 - 0.1 * dist);
}, this);
}
if (this.gameState.cursors.left.isDown) {
this.player.setVelocityX(-50);
this.player.anims.play("left", true);
} else if (this.gameState.cursors.right.isDown) {
this.player.setVelocityX(50);
this.player.anims.play("right", true);
} else if (this.gameState.cursors.up.isDown) {
this.player.setVelocityY(-50);
} else if (this.gameState.cursors.down.isDown) {
this.player.setVelocityY(-50);
} else {
this.player.setVelocityX(0);
}
updateMap();
}
}
Update
I've put the function outside the scene, no change whatsoever
new code
import Phaser from "../lib/phaser.js";
export default class Game extends Phaser.Scene {
constructor() {
super("game1");
}
preload() {
this.load.image(
"Tileset",
"https://cdn.glitch.global/cc90578e-c3d0-47c5-bb0d-f5a81263b5b6/pixil-frame-0%20(17).png?v=1675985219390"
);
this.load.tilemapTiledJSON(
"map",
"https://cdn.glitch.global/cc90578e-c3d0-47c5-bb0d-f5a81263b5b6/Map.tmj?v=1675985261369"
);
this.load.spritesheet(
"player",
"https://cdn.glitch.global/cc90578e-c3d0-47c5-bb0d-f5a81263b5b6/pixil-frame-0%20(13).png?v=1675904194091",
{ frameWidth: 32, frameHeight: 32 }
);
}
create() {
this.gameState = {
map: "",
cursors: "",
};
this.gameState.map = this.make.tilemap({ key: "map" });
this.tiles = this.gameState.map.addTilesetImage("tileSet", "Tileset");
this.worldLayer = this.gameState.map.createLayer(
"Block Layer",
this.tiles,
0,
0
);
this.player = this.physics.add.sprite(0, 0, "player", 1);
this.cameras.main.setBounds(
0,
0,
this.gameState.map.widthInPixels,
this.gameState.map.heightInPixels
);
this.cameras.main.startFollow(this.player);
this.gameState.cursors = this.input.keyboard.createCursorKeys();
}
update() {
if (this.gameState.cursors.left.isDown) {
this.player.setVelocityX(-50);
this.player.anims.play("left", true);
} else if (this.gameState.cursors.right.isDown) {
this.player.setVelocityX(50);
this.player.anims.play("right", true);
} else if (this.gameState.cursors.up.isDown) {
this.player.setVelocityY(-50);
} else if (this.gameState.cursors.down.isDown) {
this.player.setVelocityY(-50);
} else {
this.player.setVelocityX(0);
}
updateMap();
}
}
function updateMap() {
console.log(this.gameState);
var origin = this.gameState.map.getTileAtWorldXY(
this.player.x,
this.player.y
);
this.gameState.map.forEachTile(function (tile) {
var dist = Phaser.Math.Distance.Chebyshev(
origin.x,
origin.y,
tile.x,
tile.y
);
tile.setAlpha(1 - 0.1 * dist);
}, this);
}
I think the problem can be solved by moving the function updateMap out of the update function/method. Making updateMap a function/mehtod of the Game class.
Like this the this inside of the updateMap function should be pointing to the correct object.
Sample Code snippet (updated):
update() {
if (this.gameState.cursors.left.isDown) {
...
}
...
// call the method
this.updateMap();
}
// the function is now a method of the class
updateMap() {
console.log(this.gameState)
var origin = this.gameState.map.getTileAtWorldXY(
this.player.x,
this.player.y
);
...
}
...
Related
I need to achieve the tree view (Go JS Tree View). The respective tree view sample source code without React JS is at (Tree View Source Code). I'm trying to do the same using React JS and have the following code written. But somehow I'm missing something and the diagram/tree view is not rendering. Can you please help me to figure out the issue?
import React from 'react';
import * as go from 'gojs';
import { ReactDiagram } from 'gojs-react';
import '../../../App.css';
go.Shape.defineFigureGenerator("ExpandedLine", function(shape, w, h) {
return new go.Geometry()
.add(new go.PathFigure(0, 0.25*h, false)
.add(new go.PathSegment(go.PathSegment.Line, .5 * w, 0.75*h))
.add(new go.PathSegment(go.PathSegment.Line, w, 0.25*h)));
});
// use a sideways V figure instead of PlusLine in the TreeExpanderButton
go.Shape.defineFigureGenerator("CollapsedLine", function(shape, w, h) {
return new go.Geometry()
.add(new go.PathFigure(0.25*w, 0, false)
.add(new go.PathSegment(go.PathSegment.Line, 0.75*w, .5 * h))
.add(new go.PathSegment(go.PathSegment.Line, 0.25*w, h)));
});
let nodeDataArray = [{ key: 0 }];
const initDiagram = () => {
let $ = go.GraphObject.make;
const diagram =
$(go.Diagram, "myDiagramDiv",
{
allowMove: false,
allowCopy: false,
allowDelete: false,
allowHorizontalScroll: false,
layout:
$(go.TreeLayout,
{
alignment: go.TreeLayout.AlignmentStart,
angle: 0,
compaction: go.TreeLayout.CompactionNone,
layerSpacing: 16,
layerSpacingParentOverlap: 1,
nodeIndentPastParent: 1.0,
nodeSpacing: 0,
setsPortSpot: false,
setsChildPortSpot: false
})
});
diagram.nodeTemplate =
$(go.Node,
{ // no Adornment: instead change panel background color by binding to Node.isSelected
selectionAdorned: false,
// a custom function to allow expanding/collapsing on double-click
// this uses similar logic to a TreeExpanderButton
doubleClick: function(e, node) {
let cmd = diagram.commandHandler;
if (node.isTreeExpanded) {
if (!cmd.canCollapseTree(node)) return;
} else {
if (!cmd.canExpandTree(node)) return;
}
e.handled = true;
if (node.isTreeExpanded) {
cmd.collapseTree(node);
} else {
cmd.expandTree(node);
}
}
},
$("TreeExpanderButton",
{ // customize the button's appearance
"_treeExpandedFigure": "ExpandedLine",
"_treeCollapsedFigure": "CollapsedLine",
"ButtonBorder.fill": "whitesmoke",
"ButtonBorder.stroke": null,
"_buttonFillOver": "rgba(0,128,255,0.25)",
"_buttonStrokeOver": null
}),
$(go.Panel, "Horizontal",
{ position: new go.Point(18, 0) },
new go.Binding("background", "isSelected",
s => (s ? 'lightblue' : 'white')).ofObject(),
$(go.Picture,
{
width: 18, height: 18,
margin: new go.Margin(0, 4, 0, 0),
imageStretch: go.GraphObject.Uniform
},
// bind the picture source on two properties of the Node
// to display open folder, closed folder, or document
new go.Binding("source", "isTreeExpanded", imageConverter).ofObject(),
new go.Binding("source", "isTreeLeaf", imageConverter).ofObject()),
$(go.TextBlock,
{ font: '9pt Verdana, sans-serif' },
new go.Binding("text", "key", function(s) { return "item " + s; }))
) // end Horizontal Panel
); // end Node
diagram.linkTemplate = $(go.Link);
let max = 499;
let count = 0;
while (count < max) {
count = makeTree(3, count, max, nodeDataArray, nodeDataArray[0]);
}
diagram.model = new go.TreeModel(nodeDataArray);
return diagram;
}
function makeTree(level, count, max, nodeDataArray, parentData) {
let numChildren = Math.floor(Math.random() * 10);
for (let i = 0; i < numChildren; i++) {
if (count >= max) return count;
count++;
let childData = { key: count, parent: parentData.key };
nodeDataArray.push(childData);
if (level > 0 && Math.random() > 0.5) {
count = makeTree(level - 1, count, max, nodeDataArray, childData);
}
}
return count;
}
function imageConverter(prop, picture) {
let node = picture.part;
if (node.isTreeLeaf) {
return "images/document.svg";
} else {
if (node.isTreeExpanded) {
return "images/openFolder.svg";
} else {
return "images/closedFolder.svg";
}
}
}
window.addEventListener('DOMContentLoaded', initDiagram);
const TreeView = () => {
return (
<>
GO JS
<div id="myDiagramDiv">
<ReactDiagram
initDiagram={initDiagram}
divClassName='diagram-component'
nodeDataArray={nodeDataArray}
skipsDiagramUpdate={false}
/>
</div>
</>
);
}
export default TreeView;
When React start executing, the DOMContentLoaded event have already been fired. Instead try to call initDiagram in a useEffect hook
const TreeView = () => {
useEffect(initDiagram);
return (
<>
GO JS
<div id="myDiagramDiv">
<ReactDiagram
initDiagram={initDiagram}
divClassName='diagram-component'
nodeDataArray={nodeDataArray}
skipsDiagramUpdate={false}
/>
</div>
</>
);
}
I am making a platform game using phaser 3 and typescript. I am using a state machine and player controller to handle the logic for player movement but cant seem to figure out a way to play a falling animation ('pablo_fall') when the player is falling. I thought of maybe checking velocity.y and if its less than zero to enter the falling state but I cannot use the < operator on this.sprite.body.velocity.y. Here is the code from my playercontroller.ts file. Any help is appreciated!
import Phaser from 'phaser'
import StateMachine from '../statemachine/StateMachine'
export default class playercontroller {
private sprite: Phaser.Physics.Matter.Sprite
private StateMachine: StateMachine
private cursors: Phaser.Types.Input.Keyboard.CursorKeys
constructor(sprite: Phaser.Physics.Matter.Sprite, cursors: Phaser.Types.Input.Keyboard.CursorKeys) {
this.sprite = sprite
this.cursors = cursors
this.createAnimations()
this.StateMachine = new StateMachine(this, 'player')
this.StateMachine.addState('idle', {
onEnter: this.idleOnEnter,
onUpdate: this.idleOnUpdate
})
.addState('walk', {
onEnter: this.walkOnEnter,
onUpdate: this.walkOnUpdate
})
.addState('jump', {
onEnter: this.jumpOnEnter,
onUpdate: this.jumpOnUpdate
})
.addState('doubleJump',{
onEnter: this.doubleJumpOnEnter,
onUpdate: this.doubleJumpOnUpdate
})
.setState('idle')
this.sprite.setOnCollide((data: MatterJS.ICollisionPair) => {
if (this.StateMachine.isCurrentState('jump')||this.StateMachine.isCurrentState('doubleJump')) {
this.StateMachine.setState('idle')
}
})
}
update(dt: number) {
this.StateMachine.update(dt)
}
private idleOnEnter() {
this.sprite.play('pablo_idle')
}
private idleOnUpdate() {
if (this.cursors.left.isDown || this.cursors.right.isDown) {
this.StateMachine.setState('walk')
}
const spaceJustPressed = Phaser.Input.Keyboard.JustDown(this.cursors.space)
if (spaceJustPressed) {
this.StateMachine.setState('jump')
}
}
private walkOnEnter() {
this.sprite.play('pablo_run')
}
private walkOnUpdate() {
const speed = 5;
if (this.cursors.left.isDown) {
this.sprite.flipX = true
this.sprite.setVelocityX(-speed)
}
else if (this.cursors.right.isDown) {
this.sprite.flipX = false
this.sprite.setVelocityX(speed)
}
else {
this.sprite.setVelocityX(0)
this.StateMachine.setState('idle')
}
const spaceJustPressed = Phaser.Input.Keyboard.JustDown(this.cursors.space)
if (spaceJustPressed) {
this.StateMachine.setState('jump')
}
}
private jumpOnEnter() {
this.sprite.setVelocityY(-6)
this.sprite.play('pablo_jump')
}
private jumpOnUpdate() {
const speed = 5;
let canDoubleJump = true;
const spaceJustPressed = Phaser.Input.Keyboard.JustDown(this.cursors.space)
if (spaceJustPressed) {
this.StateMachine.setState('doubleJump')
}
if (this.cursors.left.isDown) {
this.sprite.flipX = true
this.sprite.setVelocityX(-speed)
}
else if (this.cursors.right.isDown) {
this.sprite.flipX = false
this.sprite.setVelocityX(speed)
}
}
private doubleJumpOnEnter(){
this.sprite.setVelocityY(-6)
this.sprite.play('pablo_double_jump')
}
private doubleJumpOnUpdate() {
const speed = 5;
if (this.cursors.left.isDown) {
this.sprite.flipX = true
this.sprite.setVelocityX(-speed)
}
else if (this.cursors.right.isDown) {
this.sprite.flipX = false
this.sprite.setVelocityX(speed)
}
}
private createAnimations() {
this.sprite.anims.create({
key: 'pablo_idle',
frameRate: 20,
frames: this.sprite.anims.generateFrameNames('pablo_idle', {
start: 0,
end: 10,
prefix: 'Idle (32x32)-',
suffix: '.png'
}),
repeat: -1
})
this.sprite.anims.create({
key: 'pablo_run',
frameRate: 20,
frames: this.sprite.anims.generateFrameNames('pablo_run', {
start: 0,
end: 10,
prefix: 'Run (32x32)-',
suffix: '.png'
}),
repeat: -1
})
this.sprite.anims.create({
key: 'pablo_jump',
frameRate: 20,
frames: 'pablo_jump',
repeat: -1
})
this.sprite.anims.create({
key: 'pablo_fall',
frameRate: 20,
frames: 'pablo_fall',
repeat: -1
})
this.sprite.anims.create({
key: 'pablo_double_jump',
frameRate: 20,
frames: this.sprite.anims.generateFrameNames('pablo_double_jump', {
start: 0,
end: 5,
prefix: 'Double Jump (32x32)-',
suffix: '.png',
})
})
}
}```
I have no idea why i couldnt use the < operator before, it said something about being undefined. Anyway this is the code.
if (this.sprite.body.velocity.y> 0){
this.sprite.anims.play('pablo_fall')
}
Am using vue and have installed the vue-mapbox component located here: https://soal.github.io/vue-mapbox/#/quickstart
I have updated the js and css to the latest versions also that gets added to the index.html:
<!-- Mapbox GL CSS -->
<link href="https://api.tiles.mapbox.com/mapbox-gl-js/v0.51.0/mapbox-gl.css" rel="stylesheet" />
<!-- Mapbox GL JS -->
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v0.51.0/mapbox-gl.js"></script>
I am trying to utilize this component to set the default view of the map bounds using either center or bounds or fitBounds to a list of Lng,Lat coordinates. So, basically, how to plug in lng,lat coordinates and have the map default to centering these coordinates inside of the container?
Here's a Component I created, called Map in vue to output the mapbox using the component vue-mapbox listed above:
<template>
<b-row id="map" class="d-flex justify-content-center align-items-center my-2">
<b-col cols="24" id="map-holder" v-bind:class="getMapType">
<mgl-map
id="map-obj"
:accessToken="accessToken"
:mapStyle.sync="mapStyle"
:zoom="zoom"
:center="center"
container="map-holder"
:interactive="interactive"
#load="loadMap"
ref="mapbox" />
</b-col>
</b-row>
</template>
<script>
import { MglMap } from 'vue-mapbox'
export default {
components: {
MglMap
},
data () {
return {
accessToken: 'pk.eyJ1Ijoic29sb2dob3N0IiwiYSI6ImNqb2htbmpwNjA0aG8zcWxjc3IzOGI1ejcifQ.nGL4NwbJYffJpjOiBL-Zpg',
mapStyle: 'mapbox://styles/mapbox/streets-v9', // options: basic-v9, streets-v9, bright-v9, light-v9, dark-v9, satellite-v9
zoom: 9,
map: {}, // Holds the Map...
fitBounds: [[-79, 43], [-73, 45]]
}
},
props: {
interactive: {
default: true
},
resizeMap: {
default: false
},
mapType: {
default: ''
},
center: {
type: Array,
default: function () { return [4.899, 52.372] }
}
},
computed: {
getMapType () {
let classes = 'inner-map'
if (this.mapType !== '') {
classes += ' map-' + this.mapType
}
return classes
}
},
watch: {
resizeMap (val) {
if (val) {
this.$nextTick(() => this.$refs.mapbox.resize())
}
},
fitBounds (val) {
if (this.fitBounds.length) {
this.MoveMapCoords()
}
}
},
methods: {
loadMap () {
if (this.map === null) {
this.map = event.map // store the map object in here...
}
},
MoveMapCoords () {
this.$refs.mapbox.fitBounds(this.fitBounds)
}
}
}
</script>
<style lang="scss" scoped>
#import '../../styles/custom.scss';
#map {
#map-obj {
text-align: justify;
width: 100%;
}
#map-holder {
&.map-modal {
#map-obj {
height: 340px;
}
}
&.map-large {
#map-obj {
height: 500px;
}
}
}
.mapboxgl-map {
border: 2px solid lightgray;
}
}
</style>
So, I'm trying to use fitBounds method here to get the map to initialize centered over 2 Lng,Lat coordinates here: [[-79, 43], [-73, 45]]
How to do this exactly? Ok, I think I might have an error in my code a bit, so I think the fitBounds should look something like this instead:
fitBounds: () => {
return { bounds: [[-79, 43], [-73, 45]] }
}
In any case, having the most difficult time setting the initial location of the mapbox to be centered over 2 or more coordinates. Anyone do this successfully yet?
Ok, so I wound up creating a filter to add space to the bbox like so:
Vue.filter('addSpaceToBBoxBounds', function (value) {
if (value && value.length) {
var boxArea = []
for (var b = 0, len = value.length; b < len; b++) {
boxArea.push(b > 1 ? value[b] + 2 : value[b] - 2)
}
return boxArea
}
return value
})
This looks to be good enough for now. Than just use it like so:
let line = turf.lineString(this.markers)
mapOptions['bounds'] = this.$options.filters.addSpaceToBBoxBounds(turf.bbox(line))
return mapOptions
setting the initial location of the map to be centered over 2 or
more coordinates
You could use Turf.js to calculate the bounding box of all point features and initialize the map with this bbox using the bounds map option:
http://turfjs.org/docs#bbox
https://www.mapbox.com/mapbox-gl-js/api/#map
I created a few simple functions to calculate a bounding box which contains the most southwestern and most northeastern corners of the given [lng, lat] pairs (markers). You can then use Mapbox GL JS map.fitBounds(bounds, options?) function to zoom the map to the set of markers.
Always keep in mind:
lng (lon): longitude (London = 0, Bern = 7.45, New York = -74)
→ the lower, the more western
lat: latitude (Equator = 0, Bern = 46.95, Capetown = -33.9)
→ the lower, the more southern
getSWCoordinates(coordinatesCollection) {
const lowestLng = Math.min(
...coordinatesCollection.map((coordinates) => coordinates[0])
);
const lowestLat = Math.min(
...coordinatesCollection.map((coordinates) => coordinates[1])
);
return [lowestLng, lowestLat];
}
getNECoordinates(coordinatesCollection) {
const highestLng = Math.max(
...coordinatesCollection.map((coordinates) => coordinates[0])
);
const highestLat = Math.max(
...coordinatesCollection.map((coordinates) => coordinates[1])
);
return [highestLng, highestLat];
}
calcBoundsFromCoordinates(coordinatesCollection) {
return [
getSWCoordinates(coordinatesCollection),
getNECoordinates(coordinatesCollection),
];
}
To use the function, you can just call calcBoundsFromCoordinates and enter an array containing all your markers coordinates:
calcBoundsFromCoordinates([
[8.03287, 46.62789],
[7.53077, 46.63439],
[7.57724, 46.63914],
[7.76408, 46.55193],
[7.74324, 46.7384]
])
// returns [[7.53077, 46.55193], [8.03287, 46.7384]]
Overall it might even be easier to use Mapbox' mapboxgl.LngLatBounds() function.
As mentioned in the answer from jscastro in Scale MapBox GL map to fit set of markers you can use it like this:
const bounds = mapMarkers.reduce(function (bounds, coord) {
return bounds.extend(coord);
}, new mapboxgl.LngLatBounds(mapMarkers[0], mapMarkers[0]));
And then just call
map.fitBounds(bounds, {
padding: { top: 75, bottom: 30, left: 90, right: 90 },
});
If you don't want to use yet another library for this task, I came up with a simple way to get the bounding box, here is a simplified vue component.
Also be careful when storing your map object on a vue component, you shouldn't make it reactive as it breaks mapboxgl to do so
import mapboxgl from "mapbox-gl";
export default {
data() {
return {
points: [
{
lat: 43.775433,
lng: -0.434319
},
{
lat: 44.775433,
lng: 0.564319
},
// Etc...
]
}
},
computed: {
boundingBox() {
if (!Array.isArray(this.points) || !this.points.length) {
return undefined;
}
let w, s, e, n;
// Calculate the bounding box with a simple min, max of all latitudes and longitudes
this.points.forEach((point) => {
if (w === undefined) {
n = s = point.lat;
w = e = point.lng;
}
if (point.lat > n) {
n = point.lat;
} else if (point.lat < s) {
s = point.lat;
}
if (point.lng > e) {
e = point.lng;
} else if (point.lng < w) {
w = point.lng;
}
});
return [
[w, s],
[e, n]
]
},
},
watch: {
// Automatically fit to bounding box when it changes
boundingBox(bb) {
if (bb !== undefined) {
const cb = () => {
this.$options.map.fitBounds(bb, {padding: 20});
};
if (!this.$options.map) {
this.$once('map-loaded', cb);
} else {
cb();
}
}
},
// Watch the points to add the markers
points: {
immediate: true, // Run handler on mount (not needed if you fetch the array of points after it's mounted)
handler(points, prevPoints) {
// Remove the previous markers
if (Array.isArray(prevPoints)) {
prevPoints.forEach((point) => {
point.marker.remove();
});
}
//Add the new markers
const cb = () => {
points.forEach((point) => {
// create a HTML element for each feature
const el = document.createElement('div');
el.className = 'marker';
el.addEventListener('click', () => {
// Marker clicked
});
el.addEventListener('mouseenter', () => {
point.hover = true;
});
el.addEventListener('mouseleave', () => {
point.hover = false;
});
// make a marker for each point and add to the map
point.marker = new mapboxgl.Marker(el)
.setLngLat([point.lng, point.lat])
.addTo(this.$options.map);
});
};
if (!this.$options.map) {
this.$once('map-loaded', cb);
} else {
cb();
}
}
}
},
map: null, // This is important to store the map without reactivity
methods: {
mapLoaded(map) {
this.$options.map = map;
this.$emit('map-loaded');
},
},
}
It should work fine as long as your points aren't in the middle of the pacific juggling between 180° and -180° of longitude, if they are, simply adding a check to invert east and west in the return of the bounding box should do the trick
Working from this I have the following code:
export default {
data(){
return {
width: 0,
height: 0,
}
},
methods: {
resizedWindow: _.throttle(this.reset, 200),
reset(){
this.width = window.innerWidth;
this.height = window.innerHeight;
}
},
mounted(){
this.reset();
window.addEventListener('resize', this.resizedWindow);
}
}
But this gives me the following error:
Uncaught TypeError: Cannot read property 'reset' of undefined
Usually this is to do with this and I (usually) know how to fix that. You do something like this:
// Replace previous resizeWindow with...
resizedWindow(){
let vm = this;
_.throttle(vm.reset, 200);
},
But this never fires the reset method. I know it must be something silly or gap in my understanding of this – how can I make this work?
It seems that in your case this is referring to the window object. You can move your resizedWindow method definition to the created hook as seen here to have access to this as the Vue instance ..
export default {
data(){
return {
width: 0,
height: 0,
}
},
methods: {
reset(){
this.width = window.innerWidth;
this.height = window.innerHeight;
}
},
created() {
this.resizedWindow = _.throttle(this.reset, 200)
},
mounted(){
this.reset();
window.addEventListener('resize', this.resizedWindow);
}
}
Alternatively, you can make use of an IIFE ..
export default {
data(){
return {
width: 0,
height: 0,
}
},
methods: {
resizedWindow() { (_.throttle(this.reset, 200))() },
reset(){
this.width = window.innerWidth;
this.height = window.innerHeight;
}
},
mounted(){
this.reset();
window.addEventListener('resize', this.resizedWindow);
}
}
I am working on a small scale app that displays videos in multiple ways using a video-player component.
Currently I am implementing a stack-list, which is a container that holds video-stack components, and each stack contains one or more video-player components.
While the correct videos are loaded from the DOM, there is a noticeable multi-second lag (in terms of keyboard response) which seems to be related to the ending of the currently played video and the fetching of the next video in the stack.
How can I get rid of this lag? Videos are able to be toggled/selected via mouse hovers or WASD keyboard commands (A: previous, D: next), and the lag can cause a delay in keyboard inputs being registered.
video-stack.hbs
{{video-player highlightedStyle=(string-append stackStyle borderStyle)
looping=(is-single-video videos) videoPos=selectedVidPos
isMuted=(if (video-selected key selectedStackIndex) isMuted true)
url=(if curVideo.teaser.isUrl curVideo.teaser.fileIdentifier
(make-local-url modelIdentifier curVideo.teaser.fileIdentifier))
onClickCallback=(action 'stackClicked')
onHoverCallback=(action 'stackHovered')
onEndedCallback=(action 'getNextVid')}}
video-stack.js
import Ember from 'ember';
export default Ember.Component.extend({
selectedVidPos: 0,
selectedStackIndex: 0,
stackStyle: '',
playerSize: '',
isMuted: true,
init() {
this._super(...arguments);
switch(this.get('videos').length){
case 1:
break;
case 2:
this.set('stackStyle', 'vid-shadows--2');
break;
case 3:
this.set('stackStyle', 'vid-shadows--3');
break;
case 4:
this.set('stackStyle', 'vid-shadows--4');
break;
default:
this.set('stackStyle', 'vid-shadows--4');
break;
}
},
curVideo: Ember.computed('videos', 'selectedVidPos', function () {
return this.get('videos')[this.get('selectedVidPos')];
}),
actions: {
stackClicked() {
this.get('onClickCallback') (this.get('videos'), this.get('selectedVidPos'));
},
getNextVid() {
let arrayLength = this.get('videos').length;
//check if there is only 1 video in the stack
if (arrayLength === 1) {
return;
}
let curArrayPos = parseInt(this.get('selectedVidPos'));
this.set('selectedVidPos', (curArrayPos + 1) % arrayLength);
},
stackHovered() {
this.get('onHoverCallback') (this.get('videos'), this.get('selectedStackIndex'));
}
}
});
video-player.hbs
<video oncanplay={{action 'play'}} looping=true
onended={{action 'ended'}} src={{url}}
class="video-player__video {{highlightedStyle}} {{if playing '' 'video-
player__darken'}}" muted={{muted}} />
video-player.js
import Ember from 'ember';
export default Ember.Component.extend({
url: null,
looping: false,
playing: true,
muted: true,
highlightedStyle: '',
click(event) {
this.get('onClickCallback') (this.get('videoPos'));
event.stopPropagation();
},
mouseEnter() {
this.get('onHoverCallback') (this.get('videoPos'));
},
willClearRender() {
this.set('playingObserver', null);
this.set('urlObserver', null);
},
playingObserver: Ember.observer('playing', function() {
if (this) {
var p = this.get("playing");
var videoElement = this.$().find("video").get(0);
if (videoElement) {
if (p) {
videoElement.play();
}
else {
videoElement.pause();
}
}
else {
console.log("No video element found!");
}
}
}),
urlObserver: Ember.observer('url', function() {
if (this) {
var videoElement = this.$().find("video").get(0);
if (videoElement) {
videoElement.load();
}
else {
console.log("No video element found");
}
}
}),
actions: {
ended() {
if (this.get('looping')) {
this.$().find("video").get(0).play();
console.log("video-player ended");
}
else {
console.log(this.get('videoPos'));
this.get('onEndedCallback') (this.get('videoPos'));
}
},
play() {
if (this.get('playing')) {
this.$().find("video").get(0).play();
}
}
}
});
I can post more code if it would help shed light on the culprit, thanks!
I found the culprit of the lag. The issue was in the parent container, content-area.js, which had a resetTimeout action that was being called incorrectly, which caused the focus to cycle needlessly, resulting in the lag.
Also implemented a switch off in terms of rendering videos to ensure smooth loading from one video to the next in video-stack.js, there are now 2 video objects, A & B, which are fetched and preloaded from the blob object, showing one while the other is hidden. Once the displayed video ends, they swap out, and the next video in the stack is loaded.
video-stack.js
export default Ember.Component.extend({
selectedVidAPos: 0,
selectedVidBPos: 0,
selectedStackIndex: 0,
stackStyle: '',
playerSize: '',
isMuted: true,
showVidA: true,
init() {
...
}
},
videoA: Ember.computed('videos', 'selectedVidAPos', function () {
return this.get('videos')[this.get('selectedVidAPos')];
}),
videoB: Ember.computed('videos', 'selectedVidBPos', function () {
return this.get('videos')[this.get('selectedVidBPos')];
}),
actions: {
stackClicked() {
this.get('onClickCallback') (this.get('videos'), (this.get('showVidA') ? this.get('selectedVidAPos') : this.get('selectedVidBPos')));
},
getNextVideoA() {
let arrayLength = this.get('videos').length;
if (arrayLength === 1) {
return;
}
let curArrayPos = parseInt(this.get('selectedVidAPos'));
this.set('selectedVidAPos', (curArrayPos + 2) % arrayLength);
this.set('showVidA', false);
},
getNextVideoB(){
let arrayLength = this.get('videos').length;
if (arrayLength === 1) {
return;
}
let curArrayPos = parseInt(this.get('selectedVidBPos'));
this.set('selectedVidBPos', (curArrayPos + 2) % arrayLength);
this.set('showVidA', true);
},
stackHovered() {
this.get('onHoverCallback') (this.get('videos'), this.get('selectedStackIndex'));
}
}
});
content-area.js
import Ember from 'ember';
import KeyboardControls from '../mixins/keyboard-controls';
export default Ember.Component.extend(KeyboardControls, {
displayVideoSelect: false,
displayVideoSelectTimeout: null,
displayVideo: false,
video: null,
videoPlaying: false,
keyboard: null,
backgroundVideoPos: 0,
backgroundVideoUrl: null,
backgroundVideoKeys: null,
selectionVideos: [],
stackListData: null,
showVideoSelect: function() {
this.set('displayVideoSelect', true);
this.send('resetTimeout');
},
hideVideoSelect: function() {
this.set('displayVideoSelect', false);
clearTimeout(this.get('displayVideoSelectTimeout'));
},
pauseVideo: function() {
this.set('videoPlaying', !this.get('videoPlaying'));
this.set('displayVideoSelect', !this.get('videoPlaying'));
this.set('focus', this.get('videoPlaying'));
},
select: function() {
this.set('videoPlaying', false);
this.set('focus', false);
this.showVideoSelect();
this.send('resetTimeout');
},
cancel: function() {
this.pauseVideo();
this.send('resetTimeout');
},
goNext: function() {
this.pauseVideo();
this.send('resetTimeout');
},
goPrevious: function() {
this.pauseVideo();
this.send('resetTimeout');
},
updateFocus: function(param) {
if (param) {
this.$().attr('tabindex', 2);
this.$().focus();
}//if
else {
this.$().attr('tabindex', -2);
this.$().blur();
}//else
},
init() {
...
},
click() {
this.set('focus', false);
this.showVideoSelect();
},
actions: {
videoSelected(sender, videoData) {
...
},
videoEnded() {
this.set('focus', false);
this.showVideoSelect();
this.set('displayVideo', false);
},
cycleBackground() {
...
},
cancelPressed() {
this.cancel();
},
resetTimeout() {
let component = this;
clearTimeout(this.get('displayVideoSelectTimeout'));
let timeout = setTimeout(() => {
component.hideVideoSelect();
//This set command was responsible for the lag
component.set('focus', true);
}, this.get('data.config.ui.idle') * 1000);
this.set('displayVideoSelectTimeout', timeout);
}
}
});