Test environment: iPad6 iOS 14
I have a sample html with a iframe in it.Each time the iframe switch from webgl page to blank,memory leak occurs.Repeat this safari will finally crash and reload.
Safari memory usage can be seen from perfdog(perfdog.wetest.net).Note: filter process to "com.apple.WebKit.WebContent".
Memory leak log:
Test iframe page:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* {
box-sizing: border-box;
}
/* Create two equal columns that floats next to each other */
.column {
float: left;
width: 80%;
padding: 10px;
height: 600px;
/* Should be removed. Only for demonstration */
}
.column2 {
float: left;
width: 20%;
padding: 10px;
height: 600px;
/* Should be removed. Only for demonstration */
}
/* Clear floats after the columns */
.row:after {
content: "";
clear: both;
}
</style>
</head>
<body>
<script>
function htmlToElement(html) {
var template = document.createElement('template');
html = html.trim(); // Never return a text node of whitespace as the result
template.innerHTML = html;
return template.content.firstChild;
}
function createIframeIfNotExists() {
let iframe = document.getElementById("iframe");
if (!iframe) {
const deviceLostHtmlString = `
<iframe id="iframe" src="" width="100%" height="100%" title="Iframe Example"></iframe>
`;
iframe = htmlToElement(deviceLostHtmlString);
document.getElementById("iframe_parent").appendChild(iframe);
}
return iframe;
}
function toWebgl() {
createIframeIfNotExists().src = "https://webglsamples.org/aquarium/aquarium.html";
}
function toBlank() {
let iframe = document.getElementById("iframe");
if (iframe) {
iframe.src = "about:blank";
// iframe.contentWindow.document.write('');
// iframe.contentWindow.document.clear();
// iframe.parentNode.removeChild(iframe);
}
}
function toGitee() {
createIframeIfNotExists().src = "https://gitee.com";
}
</script>
<h2>Two Equal Columns</h2>
<div class="row">
<div id="iframe_parent" class="column" style="background-color:#aaa;">
<!-- <iframe id="iframe" src="" width="100%" height="100%" title="Iframe Example"></iframe> -->
</div>
<div class="column2" style="background-color:#bbb;">
<button onclick="toBlank()">about:blank</button>
<button onclick="toWebgl()">WebGL sample</button>
<button onclick="toGitee()">gitee</button>
</div>
</div>
</body>
</html>
Update:
Webkit bug report: https://bug.webkit.org/show_bug.cgi?id=218168
Related
I am self-learning Vue.js and need help.
I am practicing event handlers now. I want the red box to become 100px wider when I hover over it. It works fine when I replace hover with click, But does not work with hover.
Appreciate it if you can point out what is wrong whit my code.
<!DOCTYPE HTML>
<html>
<head>
<title>v-on Event Handlers</title>
<meta charset="UTF-8">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.redbox {
width: 100px;
height: 100px;
background: red;
}
.redboxwide {
width: 200px;
height: 100px;
background: red;
}
</style>
</head>
<body>
<div id="app">
<div v-bind:class="{redbox:!redboxhover, redboxwide:redboxhover}" v-on:hover="redbox();">Hover over me</div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
redboxhover: false
},
methods: {
redbox: function () {
this.redboxhover = !this.redboxhover;
}
}
});
</script>
</body>
</html>
You need to use v-on:mouseover instead of v-on:hover.
Also you might need v-on:mouseleave for cleanup.
Here you could find more info about existing Events and mouseover
Use #mouseover like this:
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.redbox {
width: 100px;
height: 100px;
background: red;
}
.redboxwide {
width: 200px;
height: 100px;
background: red;
}
</style>
<div id="app">
<div v-bind:class="{redbox:!redboxhover, redboxwide:redboxhover}" #mouseover="redbox();">Hover over me</div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
redboxhover: false
},
methods: {
redbox: function() {
this.redboxhover = !this.redboxhover;
}
}
});
</script>
With the help of #tauzN and #Alex Kosh, I changed my code to the following. I added both #mouseover and #mouseleave.
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.redbox {
width: 100px;
height: 100px;
background: red;
}
.redboxwide {
width: 200px;
height: 100px;
background: red;
}
</style>
<div id="app">
<div v-bind:class="{redbox:!redboxhover, redboxwide:redboxhover}" #mouseover="redbox();" #mouseleave="!redbox();">Hover over me</div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
redboxhover: false
},
methods: {
redbox: function() {
this.redboxhover = !this.redboxhover;
}
}
});
</script>
I have to add a custon SVG file instead of the navigation icon from ArcGIS in the 'locate' widget ('esri-icon-locate'). Here the problem is, the default icon is appearing top of the custom svg file. Is there any way to hide the default icon?
view.when(_ => {
const n = document.getElementsByClassName("esri-icon-locate");
if (n && n.length === 1) {
n[0].classList += " mapnavigation"
}
});
and the css,
.mapnavigation:before{
display: block;
background: url('mapnavigation.svg');
background-repeat: no-repeat;
background-size: 17px 17px;
background-color: #ffffff;
}
You were really close to the solution, you just need to make it the only class of the node. Take a look at the example I put for you,
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no" />
<title>Locate button | Sample | ArcGIS API for JavaScript 4.18</title>
<link rel="stylesheet" href="https://js.arcgis.com/4.18/esri/themes/light/main.css" />
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
.my-svg-icon {
background: url(https://openlayers.org/en/latest/examples/data/square.svg);
width: 20px;
height: 20px;
}
</style>
<script src="https://js.arcgis.com/4.18/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/widgets/Locate"
], function (Map, MapView, Locate) {
var map = new Map({
basemap: "topo-vector"
});
var view = new MapView({
container: "viewDiv",
map: map,
center: [-56.049, 38.485, 78],
zoom: 3
});
var locateBtn = new Locate({
view: view
});
// Add the locate widget to the top left corner of the view
view.ui.add(locateBtn, {
position: "top-left"
});
view.when(_ => {
const n = document.getElementsByClassName("esri-icon-locate");
if (n && n.length === 1) {
n[0].classList = 'my-svg-icon';
}
});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
</body>
</html>
I've been trying to get <transition-group> to work but I just can't figure it out. Upon clicking the box, the element will change its position - which is suppose to trigger v-move class. This is the specific part of Vue documentation that I'm looking at.
Here is the link to my test app
<style>
.test-group-leave-to{
opacity: 0;
}
.test-group-move{
transition: transform 5s;
}
</style>
<body>
<div id='app'>
<test-comp></test-comp>
</div>
<script>
let arrary = ['1','2','3','4','5','6','7','8','9'];
new Vue({
el: "#app",
components: {
"test-comp":{
template: `
<div style='display:inline-block;'>
<transition-group name='test-group'>
<div
class='box'
v-for='(all,ind) in arr'
:key='all'
#click='del(ind, $event)'>{{ all }}</div>
</transition-group>
</div>
`,
data(){
return {
arr: arrary
}
},
methods: {
del(ind, e){
e.target.style.left = '100px';
this.arr.splice(ind,1);
}
}
}
}
});
</script>
A bit different approach in order to achieve smoother animations:
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.19/lodash.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Balsamiq+Sans:wght#400;700&family=Indie+Flower&family=Nunito+Sans:wght#400;600;700;800;900&display=swap" rel="stylesheet">
<style>
body{
position: relative;
top: 0;
left: 0;
width: 100%;
height: 100%;
margin: 0;
font-family: 'Nunito', sans-serif;
}
.outer-box {
position: relative;
width: 100%;
padding: 5px 10px;
transition: all 1s;
}
.box{
border: 1px solid blue;
text-align: center;
width: 50px;
height: 50px;
user-select: none;
}
.animate_move-enter, .animate_move-leave-to {
opacity: 0;
transform: translateX(100px);
}
.animate_move-leave-active {
position: absolute;
}
</style>
</head>
<body>
<div id='app'>
<test-comp></test-comp>
</div>
<script>
let arrary = ['1','2','3','4','5','6','7','8','9'];
new Vue({
el: "#app",
components: {
"test-comp":{
template: `
<div>
<transition-group name='animate_move'>
<div v-for='(all,ind) in arr' :key='all' class="outer-box">
<div class='box' #click='del(ind, $event)'>{{ all }}</div>
</div>
</transition-group>
</div>
`,
data(){
return {
arr: arrary
}
},
methods: {
del(ind, e){
this.arr.splice(ind,1);
}
}
}
}
});
</script>
</body>
</html>
This way you can avoid jumping blocks after they are removed.
I suggest to use List Entering/Leaving Transitions instead of move transition and you should remove e.target.style.left = '100px'; :
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.19/lodash.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Balsamiq+Sans:wght#400;700&family=Indie+Flower&family=Nunito+Sans:wght#400;600;700;800;900&display=swap" rel="stylesheet">
<style>
body {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
margin: 0;
font-family: 'Nunito', sans-serif;
}
.box {
position: relative;
border: 1px solid blue;
margin: 10px;
text-align: center;
width: 50px;
height: 50px;
user-select: none;
}
.test-group-enter-active,
.test-group-leave-active {
transition: all 1s;
}
.test-group-enter,
.test-group-leave-to {
opacity: 0;
transform: translateX(100px);
}
</style>
</head>
<body>
<div id='app'>
<test-comp></test-comp>
</div>
<script>
let arrary = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
new Vue({
el: "#app",
components: {
"test-comp": {
template: `
<div style='display:inline-block;'>
<transition-group name='test-group'>
<div
class='box'
v-for='(all,ind) in arr'
:key='all'
#click='del(ind, $event)'>{{ all }}</div>
</transition-group>
</div>
`,
data() {
return {
arr: arrary
}
},
methods: {
del(ind, e) {
//e.target.style.left = '100px';
this.arr.splice(ind, 1);
}
}
}
}
});
</script>
</body>
</html>
I need to create a simple webpage where I can see the result of filters in arc online into it instead of creating the filter each day. By that, the results will be refreshed automatically each day when I log in into arc gis.
I heard that there is an API, is my idea is feasible ?
Assuming you want to filter a FeatureLayer--the original question is unclear, but FeatureLayer seems logical--you should set the definitionExpression property, as described in the FeatureLayer documentation.
Here's an example:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<title>FeatureLayer - 4.3</title>
<link rel="stylesheet" href="https://js.arcgis.com/4.3/esri/css/main.css">
<script src="https://js.arcgis.com/4.3/"></script>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#filterDiv {
position: absolute;
top: 12px;
right: 12px;
padding: 12px;
background-color: rgba(0, 0, 0, 0.5);
color: white;
}
</style>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"dojo/dom",
"dojo/on",
"dojo/domReady!"
],
function(
Map,
MapView,
FeatureLayer,
dom,
on
) {
var map = new Map({
basemap: "hybrid"
});
var view = new MapView({
container: "viewDiv",
map: map,
extent: {
xmin: -9177811,
ymin: 4247000,
xmax: -9176791,
ymax: 4247784,
spatialReference: 102100
}
});
var featureLayer = new FeatureLayer({
url: "https://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/Landscape_Trees/FeatureServer/0",
definitionExpression: "Sci_Name = 'Ulmus pumila'"
});
map.add(featureLayer);
view.then(function() {
on(dom.byId("filterInput"), "change", updateFilter);
function updateFilter(ev) {
featureLayer.definitionExpression = ev.target.checked ?
"Sci_Name = 'Ulmus pumila'" :
undefined;
}
});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
<div id="filterDiv">
<label>Filter
<input id="filterInput" type="checkbox" checked="yes">
</label>
</div>
</body>
</html>
i am trying to display navigation tool and switch base map.Individually both are working good when i combine it its showing Uncaught Type Error: Cannot read property 'on' of undefined.can any tell me what is the mistake
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
<title></title>
<link rel="stylesheet" href="https://js.arcgis.com/3.15/dijit/themes/claro/claro.css">
<link rel="stylesheet" href="https://js.arcgis.com/3.15/esri/css/esri.css">
<style>
html, body, #map { height: 100%; width: 100%; margin: 0; padding: 0; }
#switch{
position:absolute;
right:20px;
top:10px;
z-Index:999;
}
#basemapGallery{
width:380px;
height:280px;
}
#HomeButton {
position: absolute;
top: 95px;
left: 20px;
z-index: 50;
}
#navToolbar{
display: block;
position: absolute;
z-index: 2;
top: 10px;
left:2px
}
.zoominIcon {
display: block;
position: absolute;
width: 16px;
height: 16px;
}
.zoomoutIcon {
position: absolute;
width: 16px;
height: 16px;
}
.zoomfullextIcon {
position: absolute;
width: 16px;
height: 16px;
}
.zoomprevIcon {
position: absolute;
width: 16px;
height: 16px;
}
.zoomnextIcon {
position: absolute;
width: 16px;
height: 16px;
}
.panIcon {
position: absolute;
width: 16px;
height: 16px;
}
.deactivateIcon {
position: absolute;
width: 16px;
height: 16px;
}
</style>
<script src="https://js.arcgis.com/3.15/"></script>
<script>
var map;
require([
"esri/map",
"esri/dijit/BasemapGallery",
"esri/dijit/HomeButton",
"esri/toolbars/navigation",
"dojo/on",
"dojo/parser",
"dijit/registry",
"dijit/Toolbar",
"dijit/form/Button",
"dojo/domReady!"
], function(
Map,
BasemapGallery,
HomeButton,
Navigation,
on,
parser,
registry
) {
parser.parse();
var navToolbar;
map = new Map("map", {
basemap: "topo",
center: [-105.255, 40.022],
zoom: 13,
slider:false
});
//add the basemap gallery, in this case we'll display maps from ArcGIS.com including bing maps
var basemapGallery = new BasemapGallery({
showArcGISBasemaps: true,
map: map
}, "basemapGallery");
basemapGallery.on('load',function(){
basemapGallery.remove('basemap_1');
basemapGallery.remove('basemap_2');
basemapGallery.remove('basemap_3');
basemapGallery.remove('basemap_4');
basemapGallery.remove('basemap_5');
basemapGallery.remove('basemap_8');
});
basemapGallery.startup();
basemapGallery.on("error", function(msg) {
console.log("basemap gallery error: ", msg);
});
var home = new HomeButton({
map: map
}, "HomeButton");
home.startup();
navToolbar = new Navigation(map);
on(navToolbar, "onExtentHistoryChange", extentHistoryChangeHandler);
registry.byId("zoomin").on("click", function () {
navToolbar.activate(Navigation.ZOOM_IN);
});
registry.byId("zoomout").on("click", function () {
navToolbar.activate(Navigation.ZOOM_OUT);
});
registry.byId("zoomfullext").on("click", function () {
navToolbar.zoomToFullExtent();
});
registry.byId("zoomprev").on("click", function () {
navToolbar.zoomToPrevExtent();
});
registry.byId("zoomnext").on("click", function () {
navToolbar.zoomToNextExtent();
});
registry.byId("pan").on("click", function () {
navToolbar.activate(Navigation.PAN);
});
registry.byId("deactivate").on("click", function () {
navToolbar.deactivate();
});
function extentHistoryChangeHandler () {
registry.byId("zoomprev").disabled = navToolbar.isFirstExtent();
registry.byId("zoomnext").disabled = navToolbar.isLastExtent();
}
});
</script>
</head>
<body class="claro">
<div id="map">
<div id="navToolbar" data-dojo-type="dijit/Toolbar">
<div data-dojo-type="dijit/form/Button" id="zoomin" data-dojo-props="iconClass:'zoominIcon'">Zoom In</div>
<div data-dojo-type="dijit/form/Button" id="zoomout" data-dojo-props="iconClass:'zoomoutIcon'">Zoom Out</div>
<div data-dojo-type="dijit/form/Button" id="zoomfullext" data-dojo-props="iconClass:'zoomfullextIcon'">Full Extent</div>
<div data-dojo-type="dijit/form/Button" id="zoomprev" data-dojo-props="iconClass:'zoomprevIcon'">Prev Extent</div>
<div data-dojo-type="dijit/form/Button" id="zoomnext" data-dojo-props="iconClass:'zoomnextIcon'">Next Extent</div>
<div data-dojo-type="dijit/form/Button" id="pan" data-dojo-props="iconClass:'panIcon'">Pan</div>
<div data-dojo-type="dijit/form/Button" id="deactivate" data-dojo-props="iconClass:'deactivateIcon'">Deactivate</div>
</div>
<div id="HomeButton"></div>
<div id="switch" data-dojo-type="dijit/TitlePane" data-dojo-props="title:'Switch Basemap', closable:false, open:false">
<div id="basemapGallery"></div>
</div>
</div>
</body>
</html>
parser.parse returns a deferred in dojo 1.8+
what this means is that after
parser.parse()
your widgets are not necessarily instantiated and ready to be referenced as widgets via dijit/registry.
Also there is this is directly from the Dojo reference guide:
Note that waiting for dojo/domReady! to fire is often not sufficient when working with widgets. Many widgets shouldn’t be initialized or accessed until the following modules load and execute:
dojo/uacss
dijit/hccss
dojo/parser
Thus when working with widgets you should generally put your code inside of a dojo/ready() callback:
you do this by including "dojo/ready" in your require array and then wrapping any widget code in
ready(function(){
...your widget code....
})
in your case you could probably just wrap your entire javascript code in a ready function
require([
"esri/map",
"esri/dijit/BasemapGallery",
"esri/dijit/HomeButton",
"esri/toolbars/navigation",
"dojo/on",
"dojo/parser",
"dijit/registry",
"dojo/ready",
"dijit/Toolbar",
"dijit/form/Button",
"dojo/domReady!"
], function(
Map,
BasemapGallery,
HomeButton,
Navigation,
on,
parser,
registry,
ready
) {
ready(function() {
var navToolbar;
map = new Map("map", {
basemap: "topo",
center: [-105.255, 40.022],
zoom: 13,
slider: false
});
...etc
I also like to use parseOnLoad = true which I find to be less prone to errors (both human and otherwise)
Just put this script element above the arcgis js script tag like so
<script type="text/javascript">
var dojoConfig = {
parseOnLoad: true
};
</script>
<script src="https://js.arcgis.com/3.15/"></script>
and get rid of the call to parser.parse() at the top of your script.