Toggle navigation
在线编辑器
在线代码
文本比较
jQuery下载
前端库
在线手册
登录/注册
下载代码
html
css
js
分享到微信朋友圈
X
html
css
body { background: black; margin: 0; padding: 0; overflow: hidden; } canvas { background: transparent; background-image: linear-gradient(black 20%, #101 30%, #211 40%, #070702 52%, #000 90%, #000 100%); background-repeat: no-repeat; display: block; margin: 0 auto; width: 100%; max-width: 1800px; height: 300px; } #vignette { background-image: linear-gradient(right, black 0%, transparent 10%, transparent 90%, black 100%); position: absolute; top: 0; left: 50%; width: 100%; height: 300px; max-width: 1800px; -webkit-transform: translateX(-50%); transform: translateX(-50%); z-index: 50; }
JavaScript
console.clear() // OVERENGINEERED UNOPTIMIZED CANVAS BULLSH*T // BUT IT'S OKAY SINCE IT'S BLADE RUNNER INNIT // Some stuff left unoptimized / verbose to show the work. // TODO: // - optimize render loop, avoid overdraws, etc // - smoothly fade rows in on the horizon // Constants. Change at own risk const CANVAS_WIDTH = 900 const CANVAS_HEIGHT = 300 const FRAME_TIME = 1000 / 16 const LIGHT_ROWS = 20 const LIGHT_ROW_DEPTH = 2 const LIGHT_SPACING = 0.6 const LIGHT_SIZE = 0.1 const LIGHT_SCATTER = 0.4 const BUILDING_ROWS = 38 const BUILDING_ROW_DEPTH = 1 const BUILDING_ROW_WIDTH = 60 const BUILDING_MIN_HEIGHT = 1.5 const BUILDING_MAX_HEIGHT = 3 const STACK_HEIGHT = 9 const STACK_THRESHOLD = 0.87 const STACK_LIGHT_CHANCE = 0.95 const STACK_LIGHT_SIZE = 0.13 const FADE_GRAY_VALUE = 25 const FADE_OFFSET = 0.35 // Virtual camera. Used in perspective calculations const CAMERA = { x: 0, y: 10, z: 0, fov: 170, dist: 30, zSpeed: 0.005, } // Virtual vanishing point XY. Used in perspective calculations const VP_OFS = { x: 0.5, y: 0.27, } // Global hoisted vars for rendering contexts and timers let c, ctx, output_c, output_ctx let _t, _dt, _ft // Seedable random number generator. // Not particularly well-distributed, but fine for this case. // Allows us to emit the same set of random numbers on every frame // so we can consistently re-render the scene. const RNG = { seed: 1, random() { const x = Math.sin(RNG.seed++) * 10000 return x - (x << 0) }, randomInRange(min, max) { return ((RNG.random() * (max - min + 1)) << 0) + min } } // Module to get a random colour from a predefined list. // Uses the seedable RNG const Palette = (() => { const PAL = ['black', '#111', '#113', 'white', 'sliver', '#f88', 'orange', 'oldlace', '#569'] const lastIndex = PAL.length - 1 function getRandomFromPalette() { return PAL[RNG.randomInRange(0, lastIndex)] } return { getRandom: getRandomFromPalette } })() function ceil(n) { var f = (n << 0), f = f == n ? f : f + 1 return f } // Update method of main loop function update() { // Update our global timestamp (used in rendering) _t = Date.now() * 0.001 // Move the camera slowly 'forward' CAMERA.z += CAMERA.zSpeed } // Draw a frame of the scene. // Uses the current timestamp and the seeded RNG to render a // pseudorandom cityscape with lights and buildings. // We always generate and draw a set amount of city in front of // the camera, so it appears to be endless as we 'fly over' it. // // 1. Clear the whole scene // 2. Render random rows of lights // 3. Render random rows of buildings // 4. Blit scene to onscreen canvas let _$ = { vPointX: 0, vPointY: 0, rowScreenX: 0, MAX_LIGHTS: 0, closestLightRow: 0, rowZ: 0, rowRelativeZ: 0, scalingFactor: 0, rowScreenWidth: 0, rowScreenHeight: 0, rowScreenY: 0, rowScreenLightSpacing: 0, rowLightCount: 0, lightSize: 0, lightHalfSize: 0, lightScreenX: 0, lightScreenY: 0, closestBuildingRow: 0, rowBuildingCount: 0, rowBuildingScreenWidth: 0, rowShade: 0, rowStyleString: '', lightData: [], isStack: false, buildingHeight: 0, buildingScreenHeight: 0, buildingScreenX: 0, buildingScreenY: 0, lightSize: 0, lightHalfSize: 0, lightColor: 0, } function render() { // Calculate the pixel XY of the vanishing point // (could be done on init, but useful if we ever want to // dynamically move the camera) _$.vPointX = c.width * VP_OFS.x >> 0 _$.vPointY = c.height * VP_OFS.y >> 0 // If we wanted to, we could give each row an X offset // and include it in perspective calculations, // but we just use the centre alignment for each one here. _$.rowScreenX = CAMERA.x + _$.vPointX // 1. Clear the whole scene... // (canvases are transparent so that the CSS 'sky' gradient can be seen) ctx.clearRect(0, 0, c.width, c.height) output_ctx.clearRect(0, 0, output_c.width, output_c.height) // 2. Render random rows of lights... // Calculate the closest row to the camera so we // can render the required number of rows into the distance _$.closestLightRow = Math.floor(CAMERA.z / LIGHT_ROW_DEPTH) // Draw each row of lights for (let i = 0; i < LIGHT_ROWS; i++) { // Calculate this row's base Z position // and Z relative to camera _$.rowZ = (_$.closestLightRow * LIGHT_ROW_DEPTH) + (LIGHT_ROW_DEPTH * i) _$.rowRelativeZ = _$.rowZ - CAMERA.z // Don't draw the row if it's behind the camera, // or beyond the camera's draw distance if (_$.rowRelativeZ <= 0 || _$.rowRelativeZ > CAMERA.dist) { continue } // Get the perspective scaling factor and pixel Y position for this row _$.scalingFactor = CAMERA.fov / _$.rowRelativeZ _$.rowScreenY = CAMERA.y * _$.scalingFactor + _$.vPointY // Don't draw the row if it's off-canvas if (_$.rowScreenY > c.height) { continue } // Calculate the spacing and number of lights we need to render for this row _$.rowScreenLightSpacing = LIGHT_SPACING * _$.scalingFactor _$.rowLightCount = c.width / _$.rowScreenLightSpacing // Seed the RNG in a way that gets us decent distribution // for the random lights RNG.seed = _$.rowZ * 0.573 // Render the random lights for this row for (let j = 0; j < _$.rowLightCount; j++) { // Randomize light size, with perspective _$.lightSize = RNG.random() * (LIGHT_SIZE * _$.scalingFactor) _$.lightHalfSize = _$.lightSize * 0.5 // Randomly offset the XY of the light, with perspective _$.lightScreenX = (j * _$.rowScreenLightSpacing) + (RNG.random() * LIGHT_SCATTER * _$.scalingFactor) - _$.lightHalfSize _$.lightScreenY = (_$.rowScreenY + (RNG.random() * LIGHT_SCATTER) * _$.scalingFactor) - _$.lightHalfSize // Don't render if the light is offscreen if (_$.lightScreenX < 0 || _$.lightScreenX > c.width || _$.lightScreenY > c.height) { // HACK: we still need to call the RNG the same number of times // for every row to ensure consistency between frames. If we didn't // do this, the lights would jump all over the place near the edges // of the screen. Palette.getRandom() continue } // Pick a random colour for this light ctx.fillStyle = Palette.getRandom() // Render the light twice, mirrored either side of the centre vanishing point. // Saves us having to do perspective offset calculation for every light, // and won't be noitceable when we overlay the city buildings. ctx.fillRect((_$.rowScreenX + _$.lightScreenX), _$.lightScreenY, _$.lightSize, _$.lightSize) ctx.fillRect((_$.rowScreenX - _$.lightScreenX), _$.lightScreenY, _$.lightSize, _$.lightSize) } } // 3. Render random rows of buildings... // Calculate the closest row to the camera so we // can render the required number of rows into the distance _$.closestBuildingRow = Math.floor(CAMERA.z / BUILDING_ROW_DEPTH) // Draw each row of buildings for (let i = BUILDING_ROWS; i > 0; i--) { // Calculate this row's base Z position // and Z relative to camera _$.rowZ = (_$.closestBuildingRow * BUILDING_ROW_DEPTH) + (BUILDING_ROW_DEPTH * i) _$.rowRelativeZ = _$.rowZ - CAMERA.z // Don't draw the row if it's behind the camera, // or beyond the camera's draw distance if (_$.rowRelativeZ <= 0 || _$.rowRelativeZ > CAMERA.dist) { continue } // Get the perspective scaling factor and pixel Y position for this row _$.scalingFactor = CAMERA.fov / _$.rowRelativeZ // Calculate the perspective-scaled position and base size of our row. // Offset the XY so that the row's 'origin' is at centre bottom (i.e. ground-up) _$.rowScreenWidth = BUILDING_ROW_WIDTH * _$.scalingFactor; _$.rowScreenHeight = BUILDING_MAX_HEIGHT * _$.scalingFactor; _$.rowScreenX = CAMERA.x * _$.scalingFactor + _$.vPointX - (_$.rowScreenWidth * 0.5) _$.rowScreenY = CAMERA.y * _$.scalingFactor + _$.vPointY - _$.rowScreenHeight // Seed the RNG to keep rendering consistent for this row RNG.seed = _$.rowZ // Calculate a random number of buildings for this row // and get their screen width _$.rowBuildingCount = RNG.randomInRange(20, 70) _$.rowBuildingScreenWidth = _$.rowScreenWidth / _$.rowBuildingCount // Calculate the shade we want the buildings in this row to be. // The tint is darker nearer the camera, giving a sort of crude distance fog // near the horizon. _$.rowShade = Math.round(FADE_GRAY_VALUE * (_$.rowRelativeZ / (CAMERA.dist) - FADE_OFFSET)) _$.rowStyleString = 'rgb(' + _$.rowShade + ',' + _$.rowShade + ',' + _$.rowShade + ')' // Calclate and render each building _$.lightData.length = 0 ctx.fillStyle = _$.rowStyleString for (let j = 0; j < _$.rowBuildingCount; j++) { // Buildings have a certain chance to become a 'stack' i.e. way taller than // everything else. We calculate a random ranged height for the building, // and if it exceeds a threshold, it gets turned into a stack. _$.isStack = false _$.buildingHeight = Math.max(BUILDING_MIN_HEIGHT, RNG.random() * BUILDING_MAX_HEIGHT) if (_$.buildingHeight > (BUILDING_MAX_HEIGHT * STACK_THRESHOLD)) { _$.isStack = true // Stacks have 40% height variance _$.buildingHeight = (STACK_HEIGHT * 0.6 + (RNG.random() * 0.4)) } // Calculate the pixel size and position of this building, adjusted for perspective _$.buildingScreenHeight = _$.buildingHeight * _$.scalingFactor _$.buildingScreenX = _$.rowScreenX + (j * _$.rowBuildingScreenWidth) _$.buildingScreenY = _$.rowScreenY + _$.rowScreenHeight - _$.buildingScreenHeight // Draw the building on screen ctx.fillRect(_$.buildingScreenX, _$.buildingScreenY, Math.ceil(_$.rowBuildingScreenWidth), _$.buildingScreenHeight) // Seed the RNG for consistency when calculating stack lights (if needed) RNG.seed = _$.buildingHeight + j // Stacks have a chance to get lights on their top corners. // Generate and store light data so we can render it on top of the buildings if (_$.isStack && RNG.random() < STACK_LIGHT_CHANCE) { // Get random light size and color. // Slightly higher chance of red vs white lights _$.lightSize = RNG.random() * (STACK_LIGHT_SIZE * _$.scalingFactor) _$.lightColor = (RNG.random() > 0.6) ? 'white' : 'red' // Save light info for rendering after we do all the buildings // (helps minimixe changes to ctx.fillStyle) _$.lightData.push(_$.buildingScreenX) _$.lightData.push(_$.buildingScreenY) _$.lightData.push(_$.lightSize) _$.lightData.push(_$.lightColor) } } // Draw any lights on stacks that need them in this row for (let j = 0; j < _$.lightData.length; j += 4) { _$.buildingScreenX = _$.lightData[j] _$.buildingScreenY = _$.lightData[j+1] _$.lightSize = _$.lightData[j+2] _$.lightHalfSize = _$.lightSize * 0.5 _$.lightColor = _$.lightData[j+3] // Draw lights centred at the top left and right corners of the stack ctx.fillStyle = _$.lightColor ctx.fillRect(_$.buildingScreenX - _$.lightHalfSize, _$.buildingScreenY - _$.lightHalfSize, _$.lightSize, _$.lightSize) ctx.fillRect(_$.buildingScreenX + _$.rowBuildingScreenWidth - _$.lightHalfSize, _$.buildingScreenY - _$.lightHalfSize, _$.lightSize, _$.lightSize) } } // 4. Blit scene to onscreen canvas. // Now that we've built up the scene in-memory, we just render the image to // our canvas in the DOM. output_ctx.drawImage(c, 0, 0) } // Main loop. // Maintains a consistent update rate, but draws the screen as often // as the browser will allow. function frame() { requestAnimationFrame(frame) _ft = Date.now() update() if (_ft - _dt > FRAME_TIME) { render() _dt = _ft } } // Let's go! function start() { // Init frame timers (see frame()) _dt = _ft = Date.now() // Create two canvases - one for in-memory compositing, // and another to go in the DOM for our final render. // Make them the same size as each other. c = document.createElement('canvas') ctx = c.getContext('2d') output_c = document.createElement('canvas') output_ctx = output_c.getContext('2d') output_c.width = c.width = CANVAS_WIDTH output_c.height = c.height = CANVAS_HEIGHT document.body.appendChild(output_c) // Start the main loop. frame() } start()
粒子
时间
文字
hover
canvas
3d
游戏
音乐
火焰
水波
轮播图
鼠标跟随
动画
css
加载动画
导航
菜单
按钮
滑块
tab
弹出层
统计图
svg
×
Close
在线代码下载提示
开通在线代码永久免费下载,需支付20jQ币
开通后,在线代码模块中所有代码可终身免费下!
您已开通在线代码永久免费下载,关闭提示框后,点下载代码可直接下载!
您已经开通过在线代码永久免费下载
对不起,您的jQ币不足!可通过发布资源 或
直接充值获取jQ币
取消
开通下载
<!doctype html> <html> <head> <meta charset="utf-8"> <title>程序画布城市景观-jq22.com</title> <script src="https://www.jq22.com/jquery/jquery-1.10.2.js"></script> <style>
</style> </head> <body>
<script>
</script>
</body> </html>
2012-2021 jQuery插件库版权所有
jquery插件
|
jq22工具库
|
网页技术
|
广告合作
|
在线反馈
|
版权声明
沪ICP备13043785号-1
浙公网安备 33041102000314号