window.pageYOffset有时为0而不是有效值

时间:2018-07-05 19:43:12

标签: javascript ios wkwebview

我遇到了一个问题,就是有时候我网页上的JavaScript获得window.pageYOffset的值时,即使我知道用户正在查看文档的中间并且它的值应该很大,它还是会莫名其妙地为0。 ,例如650000。请注意,我有很大一部分时间获得了合理的价值。但是有时它是零,有时是一个看似随机的小值,例如当我期望650000时在6000范围内。

我不想问一堆代码,而是想问一些一般性问题,以帮助我弄清楚从哪里开始。

此页面正在iOS WKWebView中显示(尽管此问题可以在Android应用程序的类似上下文中显示)。我的应用程序中的JavaScript方法可以通过以下几种方式之一调用:

  1. 当我的应用程序收到页面已完成加载(通过委托方法)的通知时,它将使用Objective-C代码中的evaluateJavaScript调用JavaScript方法。

  2. 我的应用可以在其他时间调用evaluateJavaScript,而不仅仅是在页面加载完成时。

  3. 可能会由于计时器触发而调用JavaScript函数。

  4. 滚动事件的结果可能会调用JavaScript函数。

我一直在假设页面上的JavaScript代码始终在单个线程中运行。也就是说,我不会遇到计时器触发,发生滚动事件,甚至来自Objective-C代码(使用evaluateJavaScript的调用)都不会中断JavaScript运行时中可能发生的任何情况的情况。因此,在尝试访问window.pageYOffset时,我不必担心会中断某些正在修改div的系统级活动。

所以这是我的第一个问题:我是否纠正代码外的某个人在一个线程上调用我的JavaScript方法,而不是在另一个线程上胡乱使用DOM?

第二个问题与我有关:我的代码修改了DOM,添加和删除了insertAfter元素。我一直以为这些修改是同步的-如果我用insertBeforetop插入一个元素,我希望子/父/兄弟指针在返回时是正确的,并且我假设可以立即访问其他元素上的leftwindow.pageYOffset值之类的东西,它们将被更新以反映插入/删除的元素。关键是,在进行更改之后和检查类似window.pageYOffset之类的代码之前,我不必“等待” DOM“稳定”。这是正确的吗?

另一个提示:为了缓解这种情况,我很幸运,只需在函数顶部测试window.pageYOffset的零。如果它为零,我会给自己打电话回计时器(只有1毫秒的延迟)。如果我做的时间足够长,它将最终非零。

也许在阅读了所有这些内容之后,没有一个细节是相关的,并且您知道了以下基本问题的答案:为什么有时在同一行代码中,/** * Copyright 2015 IBM Corp. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; require( 'dotenv' ).config( {silent: true} ); const TextToSpeechV1 = require('watson-developer-cloud/text-to-speech/v1'); var express = require( 'express' ); // app server var bodyParser = require( 'body-parser' ); // parser for post requests var Watson = require( 'watson-developer-cloud/conversation/v1' ); // watson sdk // The following requires are needed for logging purposes var uuid = require( 'uuid' ); var vcapServices = require( 'vcap_services' ); var basicAuth = require( 'basic-auth-connect' ); // The app owner may optionally configure a cloudand db to track user input. // This cloudand db is not required, the app will operate without it. // If logging is enabled the app must also enable basic auth to secure logging // endpoints var cloudantCredentials = vcapServices.getCredentials( 'cloudantNoSQLDB' ); var cloudantUrl = null; if ( cloudantCredentials ) { cloudantUrl = cloudantCredentials.url; } cloudantUrl = cloudantUrl || process.env.CLOUDANT_URL; // || '<cloudant_url>'; var logs = null; var app = express(); // set up routes var token = require('./routes/token'); app.use('/token', token); // Bootstrap application settings app.use( express.static( './public' ) ); // load UI from public folder app.use( bodyParser.json() ); // Create the service wrapper var conversation = new Watson( { // If unspecified here, the CONVERSATION_USERNAME and CONVERSATION_PASSWORD env properties will be checked // After that, the SDK will fall back to the bluemix-provided VCAP_SERVICES environment property // username: '<username>', // password: '<password>', username: process.env.ASSISTANT_USERNAME || '<username>', password: process.env.ASSISTANT_PASSWORD || '<password>', url: 'https://gateway.watsonplatform.net/conversation/api', version_date: '2016-09-20', version: 'v1' } ); const textToSpeech = new TextToSpeechV1({ username: process.env.TEXT_TO_SPEECH_USERNAME, password: process.env.TEXT_TO_SPEECH_PASSWORD, version: 'v1' // If unspecified here, the TEXT_TO_SPEECH_USERNAME and // TEXT_TO_SPEECH_PASSWORD env properties will be checked // After that, the SDK will fall back to the bluemix-provided VCAP_SERVICES environment property // username: '<username>', // password: '<password>', }); /************************************************ * Conversation services ************************************************/ // Endpoint to be call from the client side app.post( '/api/message', function(req, res) { var workspace = process.env.WORKSPACE_ID || '<workspace-id>'; if ( !workspace || workspace === '<workspace-id>' ) { return res.json( { 'output': { 'text': 'The app has not been configured with a <b>WORKSPACE_ID</b> environment variable. Please refer to the ' + '<a href="https://github.com/watson-developer-cloud/conversation-simple">README</a> documentation on how to set this variable. <br>' + 'Once a workspace has been defined the intents may be imported from ' + '<a href="https://github.com/watson-developer-cloud/conversation-simple/blob/master/training/car_workspace.json">here</a> in order to get a working application.' } } ); } var payload = { workspace_id: workspace, context: {}, input: {} }; if ( req.body ) { if ( req.body.input ) { payload.input = req.body.input; } if ( req.body.context ) { // The client must maintain context/state payload.context = req.body.context; } } // Send the input to the conversation service conversation.message( payload, function(err, data) { if ( err ) { return res.status( err.code || 500 ).json( err ); } return res.json( updateMessage( payload, data ) ); } ); } ); /** * Updates the response text using the intent confidence * @param {Object} input The request to the Conversation service * @param {Object} response The response from the Conversation service * @return {Object} The response with the updated message */ function updateMessage(input, response) { var responseText = null; var id = null; if ( !response.output ) { response.output = {}; } else { if ( logs ) { // If the logs db is set, then we want to record all input and responses id = uuid.v4(); logs.insert( {'_id': id, 'request': input, 'response': response, 'time': new Date()}); } return response; } if ( response.intents && response.intents[0] ) { var intent = response.intents[0]; // Depending on the confidence of the response the app can return different messages. // The confidence will vary depending on how well the system is trained. The service will always try to assign // a class/intent to the input. If the confidence is low, then it suggests the service is unsure of the // user's intent . In these cases it is usually best to return a disambiguation message // ('I did not understand your intent, please rephrase your question', etc..) if ( intent.confidence >= 0.75 ) { responseText = 'I understood your intent was ' + intent.intent; } else if ( intent.confidence >= 0.5 ) { responseText = 'I think your intent was ' + intent.intent; } else { responseText = 'I did not understand your intent'; } } response.output.text = responseText; if ( logs ) { // If the logs db is set, then we want to record all input and responses id = uuid.v4(); logs.insert( {'_id': id, 'request': input, 'response': response, 'time': new Date()}); } return response; } if ( cloudantUrl ) { // If logging has been enabled (as signalled by the presence of the cloudantUrl) then the // app developer must also specify a LOG_USER and LOG_PASS env vars. if ( !process.env.LOG_USER || !process.env.LOG_PASS ) { throw new Error( 'LOG_USER OR LOG_PASS not defined, both required to enable logging!' ); } // add basic auth to the endpoints to retrieve the logs! var auth = basicAuth( process.env.LOG_USER, process.env.LOG_PASS ); // If the cloudantUrl has been configured then we will want to set up a nano client var nano = require( 'nano' )( cloudantUrl ); // add a new API which allows us to retrieve the logs (note this is not secure) nano.db.get( 'car_logs', function(err) { if ( err ) { console.error(err); nano.db.create( 'car_logs', function(errCreate) { console.error(errCreate); logs = nano.db.use( 'car_logs' ); } ); } else { logs = nano.db.use( 'car_logs' ); } } ); // Endpoint which allows deletion of db app.post( '/clearDb', auth, function(req, res) { nano.db.destroy( 'car_logs', function() { nano.db.create( 'car_logs', function() { logs = nano.db.use( 'car_logs' ); } ); } ); return res.json( {'message': 'Clearing db'} ); } ); // Endpoint which allows conversation logs to be fetched app.get( '/chats', auth, function(req, res) { logs.list( {include_docs: true, 'descending': true}, function(err, body) { console.error(err); // download as CSV var csv = []; csv.push( ['Question', 'Intent', 'Confidence', 'Entity', 'Output', 'Time'] ); body.rows.sort( function(a, b) { if ( a && b && a.doc && b.doc ) { var date1 = new Date( a.doc.time ); var date2 = new Date( b.doc.time ); var t1 = date1.getTime(); var t2 = date2.getTime(); var aGreaterThanB = t1 > t2; var equal = t1 === t2; if (aGreaterThanB) { return 1; } return equal ? 0 : -1; } } ); body.rows.forEach( function(row) { var question = ''; var intent = ''; var confidence = 0; var time = ''; var entity = ''; var outputText = ''; if ( row.doc ) { var doc = row.doc; if ( doc.request && doc.request.input ) { question = doc.request.input.text; } if ( doc.response ) { intent = '<no intent>'; if ( doc.response.intents && doc.response.intents.length > 0 ) { intent = doc.response.intents[0].intent; confidence = doc.response.intents[0].confidence; } entity = '<no entity>'; if ( doc.response.entities && doc.response.entities.length > 0 ) { entity = doc.response.entities[0].entity + ' : ' + doc.response.entities[0].value; } outputText = '<no dialog>'; if ( doc.response.output && doc.response.output.text ) { outputText = doc.response.output.text.join( ' ' ); } } time = new Date( doc.time ).toLocaleString(); } csv.push( [question, intent, confidence, entity, outputText, time] ); } ); res.csv( csv ); } ); } ); } /************************************************ * Text-to-Speech services ************************************************/ /** * Pipe the synthesize method */ app.get('/api/synthesize', (req, res, next) => { const transcript = textToSpeech.synthesize(req.query); transcript.on('response', (response) => { if (req.query.download) { if (req.query.accept && req.query.accept === 'audio/wav') { response.headers['content-disposition'] = 'attachment; filename=transcript.wav'; } else { response.headers['content-disposition'] = 'attachment; filename=transcript.ogg'; } } }); transcript.on('error', next); transcript.pipe(res); }); // Return the list of voices app.get('/api/voices', (req, res, next) => { textToSpeech.voices(null, (error, voices) => { if (error) { return next(error); } res.json(voices); }); }); require('./config/error-handler')(app); module.exports = app; 中我得到一个无效值(通常为0)其他时间为有效值。

1 个答案:

答案 0 :(得分:0)

原来的问题是,从我给WKWebView提供新的HTML字符串以呈现到告诉我完成将页面加载到现有页面之间似乎存在一段时间。仍然活跃。在这段时间内,计时器继续触发,但是某些documentwindow属性将无效。

由于调试在此环境中运行的JavaScript困难,我欺骗自己以为“ pageYOffset最终变得有效”,而实际上我看到的是新页面最终完成了加载,这个新页面正在生成对计时器功能的有效调用。

在我的特定情况下(可能不适用于所有人),我能够在计时器函数的顶部检测到window.pageYOffset的值,如果该值为0,请在短暂的延迟后回叫我自己。这使我能够处理由于某种原因window.pageYOffset仍然无效(我的测试最终将通过并且我的计时器函数将照常运行)的情况,以及一切都被抛出的情况支持新页面(在这种情况下,计时器会因为页面消失而不会触发)。