vue.js nuxt.js Creating dynamical pages and pagination - vue.js

I have several scenes that I want to include in my nuxt project.
The project should have pages, and each page has 4 scenes.
I'm currently 'coding' each page and import the scenes manually.
This is my pages tree:
pages tree
This is my first page
<template>
<main class="main">
<Scene1 />
<Scene2 />
<Scene3 />
<!-- <Scene4 /> -->
</main>
</template>
<script>
import Scene1 from './1/Scene1.vue'
import Scene2 from './1/Scene2.vue'
import Scene3 from './1/Scene3.vue'
// import Scene4 from './1/Scene4.vue'
export default {
components: {
Scene1,
Scene2,
Scene3,
// Scene4
}
}
</script>
<style scoped>
.main{
display: grid;
grid-template-columns: 50% 50%;
}
</style>
And this is how a scene looks like:
<template>
<div class="three-item">
<div class="item-title">
Color Rectangle
</div>
<p>Vertex</p>
<pre><code id="vertexShader">
void main() {
gl_Position = vec4( position, 1.0 );
}
</code></pre>
<p>Fragment</p>
<pre><code id="fragmentShader">
#ifdef GL_ES
precision mediump float;
#endif
uniform float u_time;
void main() {
// Magenta (1,0,1)
gl_FragColor = vec4(1.0,0.0,1.0,1.0);
}
</code></pre>
<div id="scene1" class="scene">
</div>
</div>
</template>
<script>
import * as Three from 'three'
import hljs from 'highlight.js'
import glsl from 'highlight.js/lib/languages/glsl';
export default{
name: 'Scene1',
data() {
return {
camera: null,
scene: null,
renderer: null,
mesh: null
}
},
methods: {
init: function() {
this.container = document.getElementById('scene1');
this.camera = new Three.PerspectiveCamera(70, this.container.clientWidth/this.container.clientHeight, 0.01, 10);
this.camera.position.z = 1;
this.scene = new Three.Scene();
this.uniforms = {
u_time: { type: "f", value: 1.0 },
u_resolution: { type: "v2", value: new Three.Vector2() },
u_mouse: { type: "v2", value: new Three.Vector2() }
};
let material = new Three.ShaderMaterial( {
uniforms: this.uniforms,
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );
let geometry = new Three.BoxBufferGeometry(0.2, 0.2, 0.2);
// let material = new Three.MeshNormalMaterial();
this.mesh = new Three.Mesh(geometry, material);
this.scene.add(this.mesh);
this.renderer = new Three.WebGLRenderer({alpha:true});
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
this.container.appendChild(this.renderer.domElement);
window.addEventListener( 'resize', this.onWindowResize, false );
hljs.registerLanguage('glsl', glsl);
document.querySelectorAll('pre code').forEach((block) => {
hljs.highlightBlock(block);
});
},
animate: function() {
requestAnimationFrame(this.animate);
this.mesh.rotation.x += 0.01;
this.mesh.rotation.y += 0.02;
this.uniforms.u_time.value += 0.05;
this.renderer.render(this.scene, this.camera);
},
onWindowResize: function() {
this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
}
},
mounted() {
this.init();
this.animate();
this.onWindowResize();
}
}
</script>
To be honest, I don't get behind the dynamical created pages with nuxt. Is there a way to have a template that fetches 4 scenes, puts them into a page and does it all over again? It could be that I want to add scenes in the middle of the pages. That would destroy my current setup.
My goal is to have something like this:
dreams tree
the page.vue is crawling through the scenes, picks 4, creates a page called '1', and does it all over again. Can someone lend me a hand with that? 😊In the examples at the nuxt homepage they use .json files. I already set up all three.js scenes, but these have javascript files in it.
Erik

Related

Multiple OpenLayers maps in Ionic Vue app

We're building an app using Ionic 6 / Vue 3 / Capacitor as our framework. On one of the pages we need to display a form which, among other inputs, contain 2 map components. The user can tap to pinpoint a geographical location in each of the maps. This is our map component:
<template>
<ion-item>
<ion-label position="stacked" color="tertiary" style="margin-bottom: 10px"
>{{ controlLabel }} (Tap to choose position)</ion-label
>
<div class="mapBox">
<div
:id="'map'+randomId"
class="map"
#click="getCoord($event)"
></div>
</div>
<div>
<ion-label color="tertiary" position="stacked">{{
$lang.Lengdegrad
}}</ion-label>
<ion-input
v-model="lon"
#change="setMarker($event)"
:controlIdLat="controlIdLat"
:data-value="valueLat"
/>
</div>
<div>
<ion-label color="tertiary" position="stacked">{{
$lang.Breddegrad
}}</ion-label>
<ion-input
v-model="lat"
#change="setMarker($event)"
:controlIdLon="controlIdLon"
:data-value="valueLon"
/>
</div>
</ion-item>
</template>
<script>
import { defineComponent } from "#vue/runtime-core";
import { IonInput, IonItem, IonLabel } from "#ionic/vue";
import Map from "ol/Map";
import View from "ol/View";
import Feature from "ol/Feature";
import Point from "ol/geom/Point";
import { Style, Icon } from "ol/style";
import { OSM, Vector as VectorSource } from "ol/source";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer";
import { fromLonLat, toLonLat } from "ol/proj";
import XYZ from "ol/source/XYZ";
import "ol/ol.css";
import PinImg from "../../resources/icons8-pin-48.png";
import { Geolocation } from "#capacitor/geolocation";
export default defineComponent({
props: [
"controlLabel",
"controlIdLat",
"controlIdLon",
"lonLatFields",
"valueLat",
"valueLon",
"setStartMarker",
],
components: {
IonInput,
IonItem,
IonLabel,
},
data() {
return {
mainMap: null,
lat: null,
lon: null,
pinLayer: null,
pinFeat: null,
$lang: this.$lang,
isOnline: this.$isOnline,
isVisible: false,
randomId: Math.random().toString(36).substr(2, 5),
};
},
mounted() {
console.log("randomid",this.randomId)
this.source = new VectorSource();
setTimeout(async () => {
if (document.readyState === "loading") {
document.addEventListener(
"DOMContentLoaded",
await this.getLocation()
);
} else {
await this.getLocation();
}
this.myMap();
this.$nextTick(() => {
if (this.setStartMarker) {
this.setMarker();
}
view.setCenter(fromLonLat([this.lon, this.lat]));
});
}, 100);
},
methods: {
async getLocation() {
const position = await Geolocation.getCurrentPosition();
this.lat = position.coords.latitude.toFixed(3);
this.lon = position.coords.longitude.toFixed(3);
},
getCoord(event) {
let lonlat = toLonLat(this.mainMap.getEventCoordinate(event));
this.lat = lonlat[1].toFixed(3);
this.lon = lonlat[0].toFixed(3);
this.$nextTick(() => {
this.setMarker();
});
},
myMap() {
this.mainMap = new Map({
layers: [
new TileLayer({
source: new OSM(),
}),
new TileLayer({
source: new XYZ({
url: "https://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=sjokartraster&zoom={z}&x={x}&y={y}",
attributions:
'Kartverket',
}),
}),
(this.pinLayer = new VectorLayer({
source: new VectorSource({
features: [],
}),
style: new Style({
image: new Icon({
anchor: [0.5, 46],
anchorXUnits: "fraction",
anchorYUnits: "pixels",
src: PinImg,
}),
}),
})),
],
target: "map" + this.randomId,
view: view,
});
console.log("map", this.mainMap);
},
setMarker() {
let p = new Point(fromLonLat([this.lon, this.lat]));
console.log("setmarker", p);
if (!this.pinLayer.getSource().getFeatures().length) {
this.pinFeat = new Feature({
geometry: p,
});
this.pinLayer.getSource().addFeature(this.pinFeat);
} else {
this.pinFeat.setGeometry(p);
}
let vals = {
[this.controlIdLat]: this.lat,
[this.controlIdLon]: this.lon,
};
this.$emit("input", vals);
console.log(p.getCoordinates());
view.setCenter(p.getCoordinates());
},
},
});
const view = new View({
center: fromLonLat([13.486, 68.131]),
zoom: 10,
constrainResolution: true,
});
</script>
<style scoped>
div.map {
border: 5px solid white;
margin: 0 auto;
height: 100%;
width: 100%;
}
div.mapBox {
width: 100%;
height: 40vh;
}
</style>
Testing in the browser, this kind of works. The user can tap each of the two maps and two separate locations will be saved to the variables. However, when panning or zooming one of the maps, the other one follows so that they both show exactly the same map area.
As you can see, I've tried assigning a random id to each map to separate them from each other. That didn't work.
Testing on an Android phone, only the latter of the two maps are displayed. The first mainMap is left undefined.
My guess is that if I can figure out the first nuisance, then the second problem will also be solved. Any tips?
Edit: I tried making a new identical map component and use one for each map. Now the maps work as expected in the browser, being controlled separately. However the first map is still undefined on Android.

D3 working properly only in first instance of Vue 3 component

I'm working on Vue 3 app where I would like to use multiple instances of one component. Each component should have its instance of D3 for displaying various SVG images. In my case D3 works as intended only on first instance of Vue component.
Dots are random generated by D3. I can see when inspecting elements that none of dots has been appended in second instance of component. Screenshot of the problem may be found here.
My component with D3 looks like this:
<template>
<div class="fill-100">
<svg ref="svgRef" width="400" height=667>
<g></g>
</svg>
</div>
</template>
<script>
import {ref, onMounted} from "#vue/runtime-core";
import {select, zoom} from "d3";
export default {
name: "SldSvgD3",
props: ["id"],
setup() {
const svgRef = ref(null);
onMounted(() =>{
const svg = select(svgRef.value);
svg.append("svg")
let data = [], width = 400, height = 667, numPoints = 100;
let zoom3 = zoom()
.on('zoom', handleZoom);
function handleZoom(e) {
select('svg g')
.attr('transform', e.transform);
}
function initZoom() {
select('svg')
.call(zoom3);
}
function updateData() {
data = [];
for(let i=0; i<numPoints; i++) {
data.push({
id: i,
x: Math.random() * width,
y: Math.random() * height
});
}
}
function update() {
select('svg g')
.selectAll('circle')
.data(data)
.join('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', 3);
}
initZoom();
updateData();
update();
});
return {svgRef}
}
}
</script>
<style lang="scss">
.fill-100{
width: 100%;
height: 100%;
}
</style>
Implementation of D3 zoom and pan taken from this site
What I didn't know is that scope of d3.select() call is global for the whole app. Solution in my case was just creating unique id for root div and selecting this div before any manipulation.
This question was very helpful to me.
Complete code:
<template>
<div class="fill-100" :id="'sld_div'+this.id">
</div>
</template>
<script>
import {ref, onMounted} from "#vue/runtime-core";
import * as d3 from "d3";
export default {
name: "SldSvgD3",
props: ["id"],
setup(props) {
const svgRef = ref(null);
const svg_width = 400;
const svg_height = 667;
onMounted(() =>{
const svg = d3
.select("#sld_div"+props.id)
svg.append("svg")
.attr("id","sld_root"+props.id)
.attr("width", svg_width)
.attr("height", svg_height)
.append("g")
.attr("id","sld_root_g"+props.id)
let data = [], width = 600, height = 400, numPoints = 100;
let zoom = d3.zoom()
.on('zoom', handleZoom);
function handleZoom(e) {
d3.select("#sld_div"+props.id)
.select('svg g')
.attr('transform', e.transform);
}
function initZoom() {
d3.select("#sld_div"+props.id)
.select('svg')
.call(zoom);
}
function updateData() {
data = [];
for(let i=0; i<numPoints; i++) {
data.push({
id: i,
x: Math.random() * width,
y: Math.random() * height
});
}
}
function update() {
d3.select("#sld_div"+props.id)
.select('svg g')
.selectAll('circle')
.data(data)
.join('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', 3);
}
initZoom();
updateData();
update();
});
return {svgRef}
}
}
</script>
<style lang="scss">
.fill-100{
width: 100%;
height: 100%;
}
</style>

GeoLocation can't pass coordinates to another variable

So i would like to pass latitude and longitude to width and height variables in "data()", instead of just writing it in html.
I tried to pass variable through all these functions to have access to variable but this doesn't worked in function showLocation(location).
<template>
<div class="app">
<div class="app-Inner">
</div>
<button #click="getLocation">VIEW YOUR LOCATION</button>
<div>{{ width }}</div>
<div class="location"></div>
<div class="map"></div>
</div>
</template>
<script>
import Home from './components/Home.vue';
import MainValues from './components/MainValues.vue';
import Form from './components/Form.vue';
import More from './components/More.vue';
export default {
name: 'App',
data() {
return {
width: 0,
height: 0,
};
},
methods: {
getLocation() {
const geo = navigator.geolocation;
if (geo) {
// eslint-disable-next-line
geo.getCurrentPosition(showLocation);
} else {
const divLocation2 = document.querySelector('.location');
divLocation2.innerHTML = 'error';
}
function showLocation(location) {
// edit: !!!
// here instead of sending location cords through innerHTML i
// want to assign them into variables "width" and "height" which
// are defined in "data()" function above.
// tried: this.width = location.coords.latitude, and
// also : this.length = location.coords.longitude
// !!!
const divLocation = document.querySelector('.location');
divLocation.innerHTML = `${location.coords.latitude} ${location.coords.longitude}`;
}
},
},
};
</script>

Vue2leaflet marker clusters not showing

I'm new to Vue JS and I'm trying to make a map with marker clusters because I'm having a great number of markers in my map. I wrote a code based on the Vue2leaflet documentation, but it doesn't show the clusters and there is no error.
I tried to use l-marker,but I was struggling with it, I was having "Cannot read property 'lat' of undefined" error and even though I was really trying to resolve this problem, at the end I decided to use l-geo-json to show the markers on the map.
<template lang="html">
<section class="charts-highcharts">
<grid-block title="Map">
<div class="-map" id="map" >
<l-map :zoom="8" :center="[47.41322, -1.219482]" :options="{ gestureHandling: true }" ref="map">
<l-tile-layer url="http://{s}.tile.osm.org/{z}/{x}/{y}.png"></l-tile-layer>
<l-marker-cluster :options="clusterOptions">
<l-geo-json v-for="l in locations" :key="l.id" :geojson="l.location" :options="options"></l-geo-json>
</l-marker-cluster>
</l-map>
</div>
</grid-block>
</section>
</template>
<script lang="js">
import L from "leaflet";
import { LMap, LTileLayer, LMarker, LGeoJson, LPopup } from "vue2-leaflet";
import { GestureHandling } from "leaflet-gesture-handling";
import Vue2LeafletMarkercluster from 'vue2-leaflet-markercluster';
import 'vue2-leaflet-markercluster/dist/leaflet.markercluster.js';
import 'vue2-leaflet-markercluster/dist/leaflet.markercluster-src.js';
export default {
name: 'Maps',
components: {
LMap,
LTileLayer,
LMarker,
LGeoJson,
LPopup,
"l-marker-cluster": Vue2LeafletMarkercluster,
},
data () {
return {
geojson: null,
clusterOptions: { disableClusteringAtZoom: 11},
locations: [],
options: {
onEachFeature: function onEachFeature(feature, layer) {
layer.bindPopup(feature.properties.name);
},
},
};
},
created () {
L.Map.addInitHook("addHandler","gestureHandling", GestureHandling);
},
mounted() {
let geojson = response.data.features,
id = 0,
tmpLocations = [];
for (let l of geojson) {
tmpLocations.push({
id: id,
location: l,
});
id++;
}
this.locations = tmpLocations;
});
},
};
</script>
<style scoped lang="scss">
#import "~leaflet.markercluster/dist/MarkerCluster.css";
#import "~leaflet.markercluster/dist/MarkerCluster.Default.css";
.charts-highcharts {
}
.-map {
width: 100%;
height: 800px;
}
</style>
Do you know why the clusters aren't showing?

Nuxt/Vue/Bootstrap-vue shrink navbar on scroll

Learning Vue with Nuxt. Want to change navbar classes depending on the page scroll position.
Looked in several places, but haven't found a working solution.
Here's what I have to work with:
``` default.vue
<template lang="pug">
div(v-on:scroll="shrinkNav", v-on:click="shrinkNav")
b-navbar.text-center(toggleable="sm" type="light" sticky v-b-scrollspy)
#myNav.mx-auto.bg-white
b-navbar-toggle(target="nav_collapse")
b-navbar-brand.mx-auto(href="#")
| Example.org
b-collapse#nav_collapse.mx-auto(is-nav='')
b-navbar-nav(justified, style="min-width: 600px").vertical-center
b-nav-item.my-auto(href='#home') Home
b-nav-item.my-auto(href='/how') How
i.fab.fa-earlybirds.fa-2x.mt-2.mb-3
b-nav-item.my-auto(href='/values') Values
b-nav-item.my-auto(href='/join-us') Join Us
#content.container(v-on:scroll="shrinkNav", v-on:click="shrinkNav")
nuxt
nuxt
</template>
<script>
// resize navbar on scroll
export default {
methods: {
shrinkNav() {
var nav = document.getElementById('nav')
var content = document.getElementById('content')
if (nav && content) {
if(content.scrollTop >= 150) {
nav.classList.add('shrink')
} else {
nav.classList.remove('shrink')
}
}
console.log(document.documentElement.scrollTop || document.body.scrollTop)
}
}
}
</script>
```
shrinkNav runs twice on click, but nothing on scroll. What is the Vue/Nuxt way to do this?
In your .vue:
<template> section:
<nav id="nav" class="navbar is-transparent is-fixed-top">
<script> section:
export default {
mounted() {
this.$nextTick(function(){
window.addEventListener("scroll", function(){
var navbar = document.getElementById("nav");
var nav_classes = navbar.classList;
if(document.documentElement.scrollTop >= 150) {
if (nav_classes.contains("shrink") === false) {
nav_classes.toggle("shrink");
}
}
else {
if (nav_classes.contains("shrink") === true) {
nav_classes.toggle("shrink");
}
}
})
})
},
}
Live Demo on codesandbox
Ok, here's a solution using Plugins. There may be a better way:
1) Define a directive in plugins/scroll.js
``` javascript
// https://vuejs.org/v2/cookbook/creating-custom-scroll-directives.html#Base-Example
import Vue from 'vue'
Vue.directive('scroll', {
inserted: function (el, binding) {
let f = function (evt) {
if (binding.value(evt, el)) {
window.removeEventListener('scroll', f)
}
}
window.addEventListener('scroll', f)
}
})
```
2) Add plugin to project in nuxt.config.js
``` javascript
module.exports = {
head: { },
plugins: [
{ src: '~/plugins/scroll.js', },
]
}
```
3) Use the v-scroll directive to define custom behavior in menu in /layouts/default.vue
``` javascript
<template lang="pug">
div(v-scroll="shrinkNav")
b-navbar.text-center(toggleable="sm" type="light" sticky)
#myNav.mx-auto.bg-white
b-navbar-toggle(target="nav_collapse")
b-navbar-brand.mx-auto(href="/#home") Example.org
b-collapse#nav_collapse.mx-auto(is-nav='')
b-navbar-nav(justified, style="min-width: 600px").vertical-center
b-nav-item.my-auto(to='/#home') Home
#content.container
nuxt
</template>
<script>
export default {
methods: {
shrinkNav() {
var scrollPosition = document.documentElement.scrollTop || document.body.scrollTop
var nav = document.getElementById('myNav')
console.log(scrollPosition, nav)
if(scrollPosition >= 150) {
nav.classList.add('shrink')
} else {
nav.classList.remove('shrink')
}
},
},
}
</script>
<style>
nav.navbar {
margin: 0;
padding: 0;
}
#myNav {
border-radius: 0 0 10px 10px;
border: 2px solid purple;
border-top: none;
}
#myNav.shrink {
border: none;
}
</style>
```