├── .gitignore ├── LICENSE ├── README.md └── examples ├── address-book ├── .eslintrc.js ├── app.js ├── app.json ├── app.wxss ├── components │ ├── address-book │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ └── navigation-bar │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss ├── pages │ └── index │ │ ├── data.js │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss ├── project.config.json ├── project.private.config.json └── sitemap.json ├── album ├── app.js ├── app.json ├── app.wxss ├── components │ ├── album │ │ ├── album-image │ │ │ ├── index.js │ │ │ ├── index.json │ │ │ ├── index.wxml │ │ │ └── index.wxss │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ ├── index.wxss │ │ └── route.js │ ├── navigation-bar │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ └── previewer │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ ├── index.wxss │ │ ├── preview-home │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ │ ├── preview-image │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ │ └── preview-list │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss ├── pages │ ├── album │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ └── preview │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss ├── project.config.json ├── project.private.config.json ├── resources │ └── back.png ├── sitemap.json └── utils │ ├── event-bus.js │ ├── store.js │ └── worklet.js ├── app-bar ├── .eslintrc.js ├── app-bar │ ├── index.js │ ├── index.json │ ├── index.wxml │ └── index.wxss ├── app.js ├── app.json ├── app.wxss ├── assets │ ├── arrow-down.png │ ├── next.png │ └── play.png ├── components │ └── navigation-bar │ │ ├── navigation-bar.js │ │ ├── navigation-bar.json │ │ ├── navigation-bar.wxml │ │ └── navigation-bar.wxss ├── pages │ ├── detail │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ └── index │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss ├── project.config.json ├── project.private.config.json └── sitemap.json ├── associated-scroll-view ├── .eslintrc.js ├── app.js ├── app.json ├── app.wxss ├── components │ └── category-list │ │ ├── category-list.js │ │ ├── category-list.json │ │ ├── category-list.wxml │ │ └── category-list.wxss ├── pages │ └── index │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss ├── project.config.json ├── project.private.config.json ├── sitemap.json └── util.js ├── card_transition ├── app.js ├── app.json ├── app.wxss ├── components │ ├── card │ │ ├── card.js │ │ ├── card.json │ │ ├── card.wxml │ │ └── card.wxss │ └── navigation-bar │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss ├── pages │ ├── detail │ │ ├── detail.js │ │ ├── detail.json │ │ ├── detail.wxml │ │ └── detail.wxss │ └── list │ │ ├── list.js │ │ ├── list.json │ │ ├── list.wxml │ │ ├── list.wxss │ │ ├── route.js │ │ └── utils.js ├── project.config.json ├── project.private.config.json └── sitemap.json ├── expanded-scroll-view ├── .eslintrc.js ├── app.js ├── app.json ├── app.wxss ├── pages │ └── index │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss ├── project.config.json ├── project.private.config.json ├── sitemap.json └── util.js ├── half-screen ├── .eslintrc.js ├── app.js ├── app.json ├── app.wxss ├── pages │ └── index │ │ ├── comment-data.js │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss ├── project.config.json ├── project.private.config.json └── sitemap.json ├── product-list ├── .eslintrc.js ├── app.js ├── app.json ├── app.wxss ├── components │ └── navigation-bar │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss ├── images │ └── search.png ├── pages │ └── index │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss ├── project.config.json ├── project.private.config.json ├── sitemap.json └── util.js ├── refresher-two-level ├── app.js ├── app.json ├── app.wxss ├── components │ └── navigation-bar │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss ├── goods.less ├── goods.wxml ├── goods │ ├── index.json │ ├── index.less │ ├── index.ts │ └── index.wxml ├── images │ ├── back_delete.png │ └── search.png ├── index │ ├── index.json │ ├── index.less │ ├── index.ts │ └── index.wxml ├── project.config.json └── util.js ├── segmented-half-screen ├── .eslintrc.js ├── app.js ├── app.json ├── app.wxss ├── pages │ └── index │ │ ├── comment-data.js │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss ├── project.config.json ├── project.private.config.json └── sitemap.json └── tab-indicator ├── .eslintrc.js ├── app.js ├── app.json ├── app.wxss ├── components └── navigation-bar │ ├── index.js │ ├── index.json │ ├── index.wxml │ └── index.wxss ├── pages └── index │ ├── index.js │ ├── index.json │ ├── index.wxml │ └── index.wxss ├── project.config.json ├── project.private.config.json └── sitemap.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 wechat-miniprogram 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # awesome-skyline 2 | 3 | Skyline 是微信小程序推出的新渲染引擎,用于替代 WebView 渲染,其能够带来更好的渲染性能,并且添加了诸多增强特性,让小程序拥有更接近原生的交互体验。更多细节可查看[文档](https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/skyline/introduction.html)。 4 | 5 | ## 示例集 6 | 7 | - [通讯录](./examples/address-book) 8 | - [相册](./examples/album) 9 | - [卡片转场](./examples/card_transition) 10 | - [半屏弹窗](./examples/half-screen) 11 | - [分段式半屏](./examples/segmented-half-screen) 12 | - [Tab 指示条](./examples/tab-indicator) 13 | - [搜索栏吸附](./examples/product-list) 14 | - [沉浸式商品浏览](./examples/expanded-scroll-view) 15 | - [分类列表联动](./examples/associated-scroll-view) 16 | -------------------------------------------------------------------------------- /examples/address-book/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Eslint config file 3 | * Documentation: https://eslint.org/docs/user-guide/configuring/ 4 | * Install the Eslint extension before using this feature. 5 | */ 6 | module.exports = { 7 | env: { 8 | es6: true, 9 | browser: true, 10 | node: true, 11 | }, 12 | ecmaFeatures: { 13 | modules: true, 14 | }, 15 | parserOptions: { 16 | ecmaVersion: 2018, 17 | sourceType: 'module', 18 | }, 19 | globals: { 20 | wx: true, 21 | App: true, 22 | Page: true, 23 | getCurrentPages: true, 24 | getApp: true, 25 | Component: true, 26 | requirePlugin: true, 27 | requireMiniProgram: true, 28 | }, 29 | // extends: 'eslint:recommended', 30 | rules: {}, 31 | } 32 | -------------------------------------------------------------------------------- /examples/address-book/app.js: -------------------------------------------------------------------------------- 1 | // app.js 2 | App({}) 3 | -------------------------------------------------------------------------------- /examples/address-book/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index" 4 | ], 5 | "window": { 6 | "backgroundTextStyle": "light", 7 | "navigationBarBackgroundColor": "#fff", 8 | "navigationBarTitleText": "Weixin", 9 | "navigationBarTextStyle": "black" 10 | }, 11 | "style": "v2", 12 | "renderer": "skyline", 13 | "rendererOptions": { 14 | "skyline": { 15 | "defaultDisplayBlock": true, 16 | "disableABTest": true, 17 | "sdkVersionBegin": "3.0.0", 18 | "sdkVersionEnd": "15.255.255" 19 | } 20 | }, 21 | "lazyCodeLoading": "requiredComponents", 22 | "componentFramework": "glass-easel", 23 | "sitemapLocation": "sitemap.json" 24 | } -------------------------------------------------------------------------------- /examples/address-book/app.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/awesome-skyline/25e2d93c2daf3f78d0908ff53ea305147d0c8978/examples/address-book/app.wxss -------------------------------------------------------------------------------- /examples/address-book/components/address-book/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {}, 4 | "addGlobalClass": false, 5 | "componentFramework": "glass-easel" 6 | } -------------------------------------------------------------------------------- /examples/address-book/components/address-book/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | {{item.alpha}} 14 | 15 | 16 | 17 | {{subItem.name}} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {{alpha}} 32 | 33 | {{alpha}} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/address-book/components/address-book/index.wxss: -------------------------------------------------------------------------------- 1 | .wx-flex { 2 | display: flex; 3 | align-items: center 4 | } 5 | 6 | .scroll-view { 7 | height: 100%; 8 | } 9 | 10 | .thin-border-bottom { 11 | position: relative 12 | } 13 | 14 | .thin-border-top { 15 | position: relative 16 | } 17 | 18 | .square-tag { 19 | color: #9a9a9a; 20 | text-align: center; 21 | height: 30px; 22 | line-height: 30px; 23 | box-sizing: border-box; 24 | border-radius: 2px; 25 | background-color: #f7f7f7; 26 | font-size: 12px; 27 | position: relative 28 | } 29 | 30 | .square-tag.selected { 31 | background: rgba(26, 173, 25, 0.1); 32 | color: #1AAD19 33 | } 34 | 35 | .select-city__hd { 36 | padding: 0 15px; 37 | position: fixed; 38 | height: 50px; 39 | background-color: #fff; 40 | left: 0; 41 | right: 0; 42 | z-index: 990 43 | } 44 | 45 | .current-city__name { 46 | display: inline-block; 47 | margin-right: 10px; 48 | margin-left: 11px 49 | } 50 | 51 | .city-group_part .city-group__title { 52 | padding-bottom: 12px 53 | } 54 | 55 | .city-group__title { 56 | padding: 12px 12px 11px 57 | } 58 | 59 | .square-tag { 60 | width: 100px; 61 | display: inline-block; 62 | margin-right: 12px; 63 | margin-bottom: 12px; 64 | height: 40px; 65 | line-height: 40px; 66 | font-size: 15px; 67 | color: #000; 68 | background-color: rgba(0, 0, 0, 0.02); 69 | overflow: hidden; 70 | white-space: nowrap; 71 | text-overflow: ellipsis 72 | } 73 | 74 | .city-group__item { 75 | padding: 15px 12px; 76 | font-size: 15px 77 | } 78 | 79 | .city-group_all { 80 | padding-bottom: 50px 81 | } 82 | 83 | .fixed__top { 84 | position: fixed; 85 | top: 0 86 | } 87 | 88 | .anchor-bar__wrp { 89 | position: fixed; 90 | top: 0; 91 | bottom: 0; 92 | right: 0; 93 | width: 30px; 94 | z-index: 999 95 | } 96 | 97 | .anchor-item { 98 | font-size: 0; 99 | text-align: center; 100 | position: relative 101 | } 102 | 103 | .anchor-item__inner { 104 | line-height: 10px; 105 | height: 14px; 106 | width: 14px; 107 | border-radius: 50%; 108 | display: flex; 109 | align-items: center; 110 | justify-content: center; 111 | font-size: 10px; 112 | margin: 1px 0; 113 | font-weight: 500 114 | } 115 | 116 | .tapped .anchor-item__pop { 117 | display: flex; 118 | } 119 | 120 | .anchor-item__pop { 121 | position: absolute; 122 | font-size: 50px; 123 | width: 55px; 124 | height: 55px; 125 | line-height: 45px; 126 | color: #fff; 127 | background-color: #C9C9C9; 128 | border-radius: 50%; 129 | border: 5px solid transparent; 130 | right: 40px; 131 | top: 50%; 132 | transform: translateY(-50%); 133 | display: none; 134 | box-sizing: border-box; 135 | align-items: center; 136 | justify-content: center; 137 | } 138 | 139 | .anchor-item__pop_after { 140 | position: absolute; 141 | width: 0; 142 | height: 0; 143 | left: 42px; 144 | border: 20px solid; 145 | border-color: transparent transparent transparent #C9C9C9; 146 | top: 50%; 147 | transform: translateY(-50%) 148 | } 149 | 150 | .anchor-item.selected .anchor-item__inner { 151 | color: #fff; 152 | background-color: #1aad19 153 | } 154 | 155 | .right-directory { 156 | position: absolute; 157 | top: 0; 158 | right: 0; 159 | z-index: 1; 160 | width: 30px; 161 | height: 100vh; 162 | justify-content: center; 163 | flex-direction: column; 164 | } 165 | .anchor-bar { 166 | display: flex; 167 | flex-direction: column; 168 | align-items: center; 169 | width: 30px; 170 | } 171 | 172 | .tips-color { 173 | color: #5d5d5d; 174 | background-color: #EAEAEA; 175 | font-weight: bold; 176 | } 177 | 178 | sticky-header { 179 | position: sticky; 180 | top: 0; 181 | z-index: 1; 182 | display: block; 183 | } 184 | -------------------------------------------------------------------------------- /examples/address-book/components/navigation-bar/index.js: -------------------------------------------------------------------------------- 1 | Component({ 2 | options: { 3 | multipleSlots: true // 在组件定义时的选项中启用多slot支持 4 | }, 5 | /** 6 | * 组件的属性列表 7 | */ 8 | properties: { 9 | extClass: { 10 | type: String, 11 | value: '' 12 | }, 13 | title: { 14 | type: String, 15 | value: '' 16 | }, 17 | background: { 18 | type: String, 19 | value: '' 20 | }, 21 | color: { 22 | type: String, 23 | value: '' 24 | }, 25 | back: { 26 | type: Boolean, 27 | value: true 28 | }, 29 | loading: { 30 | type: Boolean, 31 | value: false 32 | }, 33 | animated: { 34 | // 显示隐藏的时候opacity动画效果 35 | type: Boolean, 36 | value: true 37 | }, 38 | show: { 39 | // 显示隐藏导航,隐藏的时候navigation-bar的高度占位还在 40 | type: Boolean, 41 | value: true, 42 | observer: '_showChange' 43 | }, 44 | // back为true的时候,返回的页面深度 45 | delta: { 46 | type: Number, 47 | value: 1 48 | } 49 | }, 50 | /** 51 | * 组件的初始数据 52 | */ 53 | data: { 54 | displayStyle: '' 55 | }, 56 | attached() { 57 | const rect = wx.getMenuButtonBoundingClientRect() 58 | wx.getSystemInfo({ 59 | success: (res) => { 60 | this.setData({ 61 | statusBarHeight: res.statusBarHeight, 62 | innerPaddingRight: `padding-right:${res.windowWidth - rect.left}px`, 63 | leftWidth: `width:${res.windowWidth - rect.left}px`, 64 | navBarHeight: rect.bottom + rect.top - res.statusBarHeight, 65 | }) 66 | } 67 | }) 68 | }, 69 | /** 70 | * 组件的方法列表 71 | */ 72 | methods: { 73 | _showChange(show) { 74 | const animated = this.data.animated 75 | let displayStyle = '' 76 | if (animated) { 77 | displayStyle = `opacity: ${show ? '1' : '0'};transition: opacity 0.5s;` 78 | } else { 79 | displayStyle = `display: ${show ? '' : 'none'}` 80 | } 81 | this.setData({ 82 | displayStyle 83 | }) 84 | }, 85 | back() { 86 | const data = this.data 87 | if (data.delta) { 88 | wx.navigateBack({ 89 | delta: data.delta 90 | }) 91 | } 92 | this.triggerEvent('back', { delta: data.delta }, {}) 93 | } 94 | } 95 | }) 96 | -------------------------------------------------------------------------------- /examples/address-book/components/navigation-bar/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {}, 4 | "addGlobalClass": true, 5 | "componentFramework": "glass-easel" 6 | } -------------------------------------------------------------------------------- /examples/address-book/components/navigation-bar/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {{title}} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/address-book/components/navigation-bar/index.wxss: -------------------------------------------------------------------------------- 1 | .weui-navigation-bar { 2 | overflow: hidden; 3 | color: rgba(0, 0, 0, .9); 4 | width: 100vw; 5 | } 6 | 7 | .weui-navigation-bar__placeholder { 8 | background: #f7f7f7; 9 | position: relative; 10 | } 11 | 12 | .weui-navigation-bar__inner, .weui-navigation-bar__inner .weui-navigation-bar__left { 13 | display: flex; 14 | align-items: center; 15 | flex-direction: row; 16 | } 17 | 18 | .weui-navigation-bar__inner { 19 | position: relative; 20 | padding-right: 95px; 21 | width: 100vw; 22 | box-sizing: border-box; 23 | } 24 | 25 | .weui-navigation-bar__inner .weui-navigation-bar__left { 26 | position: relative; 27 | width: 95px; 28 | padding-left: 16px; 29 | box-sizing: border-box; 30 | } 31 | 32 | .weui-navigation-bar__btn_goback_wrapper { 33 | padding: 11px 18px 11px 16px; 34 | margin: -11px -18px -11px -16px; 35 | } 36 | 37 | .weui-navigation-bar__inner .weui-navigation-bar__left .navigation-bar__btn_goback { 38 | font-size: 12px; 39 | width: 12px; 40 | height: 24px; 41 | background: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z'/%3E%3C/svg%3E") no-repeat 50% 50%; 42 | background-size: cover; 43 | } 44 | 45 | .weui-navigation-bar__inner .weui-navigation-bar__center { 46 | font-size: 17px; 47 | text-align: center; 48 | position: relative; 49 | flex: 1; 50 | display: flex; 51 | flex-direction: column; 52 | align-items: center; 53 | justify-content: center; 54 | font-weight: bold; 55 | } 56 | 57 | @media(prefers-color-scheme: dark) { 58 | .weui-navigation-bar { 59 | color: hsla(0, 0%, 100%, .8); 60 | } 61 | .weui-navigation-bar__inner { 62 | background-color: #1f1f1f; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/address-book/pages/index/index.js: -------------------------------------------------------------------------------- 1 | import {cityData} from './data' 2 | 3 | Component({ 4 | data: { 5 | list: [], 6 | }, 7 | lifetimes: { 8 | attached() { 9 | const cities = cityData 10 | // 按拼音排序 11 | cities.sort((c1, c2) => { 12 | const pinyin1 = c1.pinyin.join('') 13 | const pinyin2 = c2.pinyin.join('') 14 | return pinyin1.localeCompare(pinyin2) 15 | }) 16 | // 添加首字母 17 | const map = new Map() 18 | for (const city of cities) { 19 | const alpha = city.pinyin[0].charAt(0).toUpperCase() 20 | if (!map.has(alpha)) map.set(alpha, []) 21 | map.get(alpha).push({ name: city.fullname }) 22 | } 23 | 24 | const keys = [] 25 | for (const key of map.keys()) { 26 | keys.push(key) 27 | } 28 | keys.sort() 29 | 30 | const list = [] 31 | for (const key of keys) { 32 | list.push({ 33 | alpha: key, 34 | subItems: map.get(key) 35 | }) 36 | } 37 | 38 | console.log('address-book list:', list) 39 | this.setData({ list }) 40 | }, 41 | }, 42 | }) 43 | -------------------------------------------------------------------------------- /examples/address-book/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "navigation-bar": "../../components/navigation-bar", 4 | "address-book": "../../components/address-book" 5 | }, 6 | "disableScroll": true, 7 | "navigationStyle": "custom" 8 | } -------------------------------------------------------------------------------- /examples/address-book/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Address Book 7 | 类通讯录列表 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/address-book/pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: flex-end; 5 | height: 100vh; 6 | background-color: #f7f7f7; 7 | } 8 | 9 | .page-container { 10 | width: 100vw; 11 | flex: 1; 12 | overflow: hidden; 13 | } 14 | 15 | .page { 16 | color: rgba(0, 0, 0, .9); 17 | font-size: 16px; 18 | font-family: -apple-system-font, Helvetica Neue, Helvetica, sans-serif; 19 | } 20 | 21 | .page__hd { 22 | padding: 40px 23 | } 24 | 25 | .page__bd { 26 | padding-bottom: 40px 27 | } 28 | 29 | .page__title { 30 | text-align: left; 31 | font-size: 20px; 32 | font-weight: 400 33 | } 34 | 35 | .page__desc { 36 | margin-top: 5px; 37 | color: rgba(0, 0, 0, .5); 38 | text-align: left; 39 | font-size: 14px 40 | } 41 | 42 | .header { 43 | height: 100px; 44 | } 45 | 46 | .cell { 47 | height: 50px; 48 | justify-content: center; 49 | align-items: center; 50 | } -------------------------------------------------------------------------------- /examples/address-book/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appid": "wxe5f52902cf4de896", 3 | "compileType": "miniprogram", 4 | "libVersion": "latest", 5 | "packOptions": { 6 | "ignore": [], 7 | "include": [] 8 | }, 9 | "setting": { 10 | "coverView": true, 11 | "es6": true, 12 | "postcss": true, 13 | "minified": true, 14 | "enhance": true, 15 | "showShadowRootInWxmlPanel": true, 16 | "packNpmRelationList": [], 17 | "babelSetting": { 18 | "ignore": [], 19 | "disablePlugins": [], 20 | "outputPath": "" 21 | }, 22 | "condition": false, 23 | "skylineRenderEnable": true, 24 | "compileWorklet": true 25 | }, 26 | "condition": {}, 27 | "editorSetting": { 28 | "tabIndent": "insertSpaces", 29 | "tabSize": 2 30 | }, 31 | "projectname": "address-book" 32 | } -------------------------------------------------------------------------------- /examples/address-book/project.private.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", 3 | "projectname": "address-book", 4 | "setting": { 5 | "compileHotReLoad": false, 6 | "skylineRenderEnable": true 7 | }, 8 | "libVersion": "latest" 9 | } -------------------------------------------------------------------------------- /examples/address-book/sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /examples/album/app.js: -------------------------------------------------------------------------------- 1 | App({}) 2 | -------------------------------------------------------------------------------- /examples/album/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/album/index", 4 | "pages/preview/index" 5 | ], 6 | "window": { 7 | "backgroundColor": "#ffffff", 8 | "backgroundTextStyle": "dark", 9 | "navigationBarBackgroundColor": "#ffffff", 10 | "navigationBarTitleText": "", 11 | "navigationBarTextStyle": "black" 12 | }, 13 | "style": "v2", 14 | "sitemapLocation": "sitemap.json", 15 | "lazyCodeLoading": "requiredComponents", 16 | "componentFramework": "glass-easel", 17 | "renderer": "skyline", 18 | "rendererOptions": { 19 | "skyline": { 20 | "defaultDisplayBlock": true, 21 | "disableABTest": true, 22 | "sdkVersionBegin": "3.0.0", 23 | "sdkVersionEnd": "15.255.255" 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /examples/album/app.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/awesome-skyline/25e2d93c2daf3f78d0908ff53ea305147d0c8978/examples/album/app.wxss -------------------------------------------------------------------------------- /examples/album/components/album/album-image/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {}, 4 | "componentFramework": "glass-easel" 5 | } -------------------------------------------------------------------------------- /examples/album/components/album/album-image/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/album/components/album/album-image/index.wxss: -------------------------------------------------------------------------------- 1 | .share-element { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | .img-cut-cnt { 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | width: 100%; 12 | height: 100%; 13 | background-color: transparent; 14 | overflow: hidden; 15 | } 16 | 17 | .img { 18 | position: absolute; 19 | top: 50%; 20 | left: 50%; 21 | width: 100%; 22 | height: 100%; 23 | transform: translate(-50%, -50%); 24 | transform-origin: center; 25 | z-index: 5; 26 | } 27 | -------------------------------------------------------------------------------- /examples/album/components/album/index.js: -------------------------------------------------------------------------------- 1 | import EventBus from '../../utils/event-bus' 2 | import { initRoute } from './route' 3 | 4 | function transformListToLineBlock(list, lineLimit) { 5 | const lineList = [] 6 | for (let i = 0, len = list.length; i < len; i += lineLimit) { 7 | const line = { index: Math.floor(i / lineLimit), list: [] } 8 | for (let j = 0; j < lineLimit; j++) { 9 | const index = i + j 10 | const item = list[index] 11 | if (item) line.list.push({ 12 | ...item, 13 | index, 14 | }) 15 | } 16 | lineList.push(line) 17 | } 18 | return lineList 19 | } 20 | 21 | Component({ 22 | properties: { 23 | list: { 24 | type: Array, 25 | value: [], 26 | }, 27 | 28 | imageWidth: { 29 | type: Number, 30 | value: 0, 31 | }, 32 | 33 | imageMargin: { 34 | type: Number, 35 | value: 0, 36 | }, 37 | 38 | lineLimit: { 39 | type: Number, 40 | value: 3, 41 | }, 42 | }, 43 | 44 | data: { 45 | showList: [], 46 | scrollIntoView: '', 47 | }, 48 | 49 | observers: { 50 | 'list, lineLimit'(list, lineLimit) { 51 | if (!list.length || !lineLimit) return 52 | 53 | // 调整为行结构,每行自己排版 54 | this.setData({ 55 | showList: transformListToLineBlock(list, lineLimit), 56 | }) 57 | }, 58 | }, 59 | 60 | lifetimes: { 61 | created() { 62 | EventBus.initWorkletEventBus(this.renderer) // 初始化 ui 线程的 eventBus 63 | initRoute() // 初始化自定义路由 64 | }, 65 | 66 | attached() { 67 | // 预览页发生图片切换时,要将对应 image 滚动到到可视范围内 68 | const pageId = this.getPageId() 69 | let scrollIntoViewTimer = null 70 | this._onPreviewerChange = image => { 71 | const list = this.data.list 72 | const index = list.findIndex(item => item.id === image.id) 73 | 74 | if (index !== -1) { 75 | if (scrollIntoViewTimer) clearTimeout(scrollIntoViewTimer) 76 | scrollIntoViewTimer = setTimeout(() => { 77 | // 可能处于页面切换动画中,故加个延迟再滚动 78 | let lineIndex = Math.floor(index / this.data.lineLimit) 79 | this.setData({ scrollIntoView: `line-${lineIndex}` }) 80 | }, 500) 81 | } 82 | } 83 | EventBus.on(`${pageId}PreviewerChange`, this._onPreviewerChange) 84 | }, 85 | 86 | detached() { 87 | const pageId = this.getPageId() 88 | EventBus.off(`${pageId}PreviewerChange`, this._onPreviewerChange) 89 | }, 90 | }, 91 | 92 | methods: { 93 | onTapImage(evt) { 94 | const { index } = evt.currentTarget.dataset || {} 95 | const image = this.data.list[index] 96 | if (!image) return 97 | 98 | // 跳转到预览页 99 | wx.navigateTo({ 100 | url: `../../pages/preview/index?imageid=${image.id}&sourcepageid=${this.getPageId()}`, 101 | routeType: 'fadeToggle', 102 | }) 103 | }, 104 | }, 105 | }) 106 | -------------------------------------------------------------------------------- /examples/album/components/album/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": { 4 | "album-image": "./album-image/index" 5 | }, 6 | "componentFramework": "glass-easel" 7 | } -------------------------------------------------------------------------------- /examples/album/components/album/index.wxml: -------------------------------------------------------------------------------- 1 | 9 | 17 | 18 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/album/components/album/index.wxss: -------------------------------------------------------------------------------- 1 | .scroll-list { 2 | width: 100%; 3 | height: 100%; 4 | overflow: hidden; 5 | } 6 | 7 | .line { 8 | display: flex; 9 | flex-direction: row; 10 | justify-content: flex-start; 11 | } 12 | 13 | .album-image { 14 | width: 100%; 15 | height: 100%; 16 | } 17 | -------------------------------------------------------------------------------- /examples/album/components/album/route.js: -------------------------------------------------------------------------------- 1 | const fastOutSlowIn = wx.worklet.Easing.bezier(0.4, 0.0, 0.2, 1.0).factory() 2 | 3 | export function initRoute() { 4 | wx.router.addRouteBuilder('fadeToggle', ({ primaryAnimation }) => { 5 | const handlePrimaryAnimation = () => { 6 | 'worklet' 7 | return { 8 | opacity: fastOutSlowIn(primaryAnimation.value), 9 | } 10 | } 11 | 12 | return { 13 | opaque: false, 14 | handlePrimaryAnimation, 15 | transitionDuration: 300, 16 | reverseTransitionDuration: 300, 17 | canTransitionTo: false, 18 | canTransitionFrom: false, 19 | } 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /examples/album/components/navigation-bar/index.js: -------------------------------------------------------------------------------- 1 | Component({ 2 | options: { 3 | multipleSlots: true // 在组件定义时的选项中启用多slot支持 4 | }, 5 | /** 6 | * 组件的属性列表 7 | */ 8 | properties: { 9 | extClass: { 10 | type: String, 11 | value: '' 12 | }, 13 | title: { 14 | type: String, 15 | value: '' 16 | }, 17 | background: { 18 | type: String, 19 | value: '' 20 | }, 21 | color: { 22 | type: String, 23 | value: '' 24 | }, 25 | back: { 26 | type: Boolean, 27 | value: true 28 | }, 29 | loading: { 30 | type: Boolean, 31 | value: false 32 | }, 33 | animated: { 34 | // 显示隐藏的时候opacity动画效果 35 | type: Boolean, 36 | value: true 37 | }, 38 | show: { 39 | // 显示隐藏导航,隐藏的时候navigation-bar的高度占位还在 40 | type: Boolean, 41 | value: true, 42 | observer: '_showChange' 43 | }, 44 | // back为true的时候,返回的页面深度 45 | delta: { 46 | type: Number, 47 | value: 1 48 | } 49 | }, 50 | /** 51 | * 组件的初始数据 52 | */ 53 | data: { 54 | displayStyle: '' 55 | }, 56 | attached() { 57 | const rect = wx.getMenuButtonBoundingClientRect() 58 | wx.getSystemInfo({ 59 | success: (res) => { 60 | this.setData({ 61 | statusBarHeight: res.statusBarHeight, 62 | innerPaddingRight: `padding-right:${res.windowWidth - rect.left}px`, 63 | leftWidth: `width:${res.windowWidth - rect.left}px`, 64 | navBarHeight: rect.bottom + rect.top - res.statusBarHeight, 65 | }) 66 | } 67 | }) 68 | }, 69 | /** 70 | * 组件的方法列表 71 | */ 72 | methods: { 73 | _showChange(show) { 74 | const animated = this.data.animated 75 | let displayStyle = '' 76 | if (animated) { 77 | displayStyle = `opacity: ${show ? '1' : '0'};transition: opacity 0.5s;` 78 | } else { 79 | displayStyle = `display: ${show ? '' : 'none'}` 80 | } 81 | this.setData({ 82 | displayStyle 83 | }) 84 | }, 85 | back() { 86 | const data = this.data 87 | if (data.delta) { 88 | wx.navigateBack({ 89 | delta: data.delta 90 | }) 91 | } 92 | this.triggerEvent('back', { delta: data.delta }, {}) 93 | } 94 | } 95 | }) 96 | -------------------------------------------------------------------------------- /examples/album/components/navigation-bar/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {}, 4 | "addGlobalClass": true, 5 | "componentFramework": "glass-easel" 6 | } -------------------------------------------------------------------------------- /examples/album/components/navigation-bar/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {{title}} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/album/components/navigation-bar/index.wxss: -------------------------------------------------------------------------------- 1 | .weui-navigation-bar { 2 | overflow: hidden; 3 | color: rgba(0, 0, 0, .9); 4 | width: 100vw; 5 | } 6 | 7 | .weui-navigation-bar__placeholder { 8 | background: #f7f7f7; 9 | position: relative; 10 | } 11 | 12 | .weui-navigation-bar__inner, .weui-navigation-bar__inner .weui-navigation-bar__left { 13 | display: flex; 14 | align-items: center; 15 | flex-direction: row; 16 | } 17 | 18 | .weui-navigation-bar__inner { 19 | position: relative; 20 | padding-right: 95px; 21 | width: 100vw; 22 | box-sizing: border-box; 23 | } 24 | 25 | .weui-navigation-bar__inner .weui-navigation-bar__left { 26 | position: relative; 27 | width: 95px; 28 | padding-left: 16px; 29 | box-sizing: border-box; 30 | } 31 | 32 | .weui-navigation-bar__btn_goback_wrapper { 33 | padding: 11px 18px 11px 16px; 34 | margin: -11px -18px -11px -16px; 35 | } 36 | 37 | .weui-navigation-bar__inner .weui-navigation-bar__left .weui-navigation-bar__btn_goback { 38 | font-size: 12px; 39 | width: 12px; 40 | height: 24px; 41 | background: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z'/%3E%3C/svg%3E") no-repeat 50% 50%; 42 | background-size: cover; 43 | } 44 | 45 | .weui-navigation-bar__inner .weui-navigation-bar__center { 46 | font-size: 17px; 47 | text-align: center; 48 | position: relative; 49 | flex: 1; 50 | display: flex; 51 | flex-direction: column; 52 | align-items: center; 53 | justify-content: center; 54 | font-weight: bold; 55 | } 56 | 57 | @media(prefers-color-scheme: dark) { 58 | .weui-navigation-bar { 59 | color: hsla(0, 0%, 100%, .8); 60 | } 61 | .weui-navigation-bar__inner { 62 | background-color: #1f1f1f; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/album/components/previewer/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": { 4 | "preview-home": "./preview-home/index" 5 | }, 6 | "componentFramework": "glass-easel" 7 | } -------------------------------------------------------------------------------- /examples/album/components/previewer/index.wxml: -------------------------------------------------------------------------------- 1 | 7 | 15 | -------------------------------------------------------------------------------- /examples/album/components/previewer/index.wxss: -------------------------------------------------------------------------------- 1 | .preview-home { 2 | width: 100%; 3 | height: 100%; 4 | display: block; 5 | } 6 | -------------------------------------------------------------------------------- /examples/album/components/previewer/preview-home/index.js: -------------------------------------------------------------------------------- 1 | import EventBus from '../../../utils/event-bus' 2 | 3 | Component({ 4 | properties: { 5 | imageId: { 6 | type: String, 7 | value: '', 8 | }, 9 | sourcePageId: { 10 | type: String, 11 | value: '', 12 | }, 13 | list: { 14 | type: Array, 15 | value: [], 16 | }, 17 | }, 18 | 19 | data: { 20 | index: 0, 21 | tempIndex: -1, 22 | pretty: false, 23 | }, 24 | 25 | observers: { 26 | tempIndex(tempIndex) { 27 | // 切换图片时会影响前一个页面的图片 28 | const { list, sourcePageId } = this.data 29 | const image = list[tempIndex] 30 | if (image) EventBus.emit(`${sourcePageId}PreviewerChange`, image) 31 | }, 32 | }, 33 | 34 | lifetimes: { 35 | attached() { 36 | const imageId = this.data.imageId 37 | const list = this.data.list 38 | let index = 0 39 | if (imageId) { 40 | const currentIndex = list.findIndex(item => item.id === imageId) 41 | if (currentIndex !== -1) index = currentIndex 42 | } 43 | 44 | this.setData({ index }) 45 | }, 46 | 47 | detached() { 48 | // 告诉前一个页面 previewer 将要销毁 49 | EventBus.emit(`${this.data.sourcePageId}PreviewerDestroy`) 50 | }, 51 | }, 52 | 53 | methods: { 54 | onBeforeRender(evt) { 55 | const { index } = evt.detail 56 | 57 | this.data.index = index // 切换可能来自 preview-list 里面,为避免造成循环触发 beforeRender,此处只改 data 不进行 setData 58 | this.setData({ tempIndex: index }) // 先更新快速预览栏 59 | }, 60 | 61 | onTapImage() { 62 | this.setData({ pretty: !this.data.pretty }) 63 | }, 64 | 65 | onBack(evt) { 66 | this.triggerEvent('back', evt.detail) 67 | }, 68 | }, 69 | }) 70 | -------------------------------------------------------------------------------- /examples/album/components/previewer/preview-home/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": { 4 | "preview-list": "../preview-list/index", 5 | "navigation-bar": "../../navigation-bar/index" 6 | }, 7 | "componentFramework": "glass-easel" 8 | } -------------------------------------------------------------------------------- /examples/album/components/previewer/preview-home/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/album/components/previewer/preview-home/index.wxss: -------------------------------------------------------------------------------- 1 | .preview-cnt { 2 | position: absolute; 3 | left: 0; 4 | top: 0; 5 | width: 100vw; 6 | height: 100vh; 7 | z-index: 5; 8 | } 9 | 10 | .preview-list { 11 | width: 100vw; 12 | height: 100vh; 13 | background-color: #fff; 14 | } 15 | 16 | .need-hide-on-back { 17 | position: absolute; 18 | top: 0; 19 | left: 0; 20 | width: 100vw; 21 | height: 100vh; 22 | } 23 | 24 | .share-element-image { 25 | position: absolute; 26 | top: 0; 27 | left: 0; 28 | width: 100vw; 29 | height: 100vh; 30 | z-index: -1; 31 | background-color: transparent; 32 | } 33 | 34 | .share-element-image .temp-image { 35 | background-color: transparent; 36 | height: 100%; 37 | width: 100%; 38 | } 39 | 40 | .share-element-image .temp-image image { 41 | width: 100%; 42 | height: 100%; 43 | } 44 | 45 | .preview-middle-self { 46 | position: absolute; 47 | height: 100vh; 48 | width: 100vw; 49 | z-index: 0; 50 | background-color: #fff; 51 | } 52 | 53 | .navigation-bar-cnt { 54 | position: absolute; 55 | top: 0; 56 | left: 0; 57 | width: 100%; 58 | z-index: 10; 59 | transition: transform .3s ease, opacity .3s ease; 60 | } 61 | 62 | .navigation-bar-cnt.show { 63 | transform: none; 64 | opacity: 1; 65 | } 66 | 67 | .navigation-bar-cnt.hide { 68 | transform: translateY(-50px); 69 | opacity: 0; 70 | } 71 | 72 | .navigation-bar { 73 | position: absolute; 74 | top: 0; 75 | left: 0; 76 | width: 100%; 77 | z-index: 5; 78 | } 79 | -------------------------------------------------------------------------------- /examples/album/components/previewer/preview-image/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {}, 4 | "componentFramework": "glass-easel" 5 | } -------------------------------------------------------------------------------- /examples/album/components/previewer/preview-image/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/album/components/previewer/preview-image/index.wxss: -------------------------------------------------------------------------------- 1 | .double-tap-gesture-handler { 2 | display: block; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | .image-wrapper { 8 | position: absolute; 9 | left: 0; 10 | top: 0; 11 | width: 100%; 12 | height: 100%; 13 | transform-origin: center; 14 | will-change: transform; 15 | } 16 | 17 | .image { 18 | width: 100%; 19 | height: 100%; 20 | } 21 | -------------------------------------------------------------------------------- /examples/album/components/previewer/preview-list/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": { 4 | "preview-image": "../preview-image/index" 5 | }, 6 | "componentFramework": "glass-easel" 7 | } -------------------------------------------------------------------------------- /examples/album/components/previewer/preview-list/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/album/components/previewer/preview-list/index.wxss: -------------------------------------------------------------------------------- 1 | 2 | .image-previewer { 3 | width: 100vw; 4 | height: 100vh; 5 | overflow: hidden; 6 | } 7 | 8 | .swiper-cnt { 9 | display: flex; 10 | flex-direction: row; 11 | position: absolute; 12 | left: 0; 13 | top: 0; 14 | width: 100vw; 15 | height: 100vh; 16 | z-index: 20; 17 | } 18 | 19 | .image { 20 | display: block; 21 | width: 100%; 22 | height: 100%; 23 | } 24 | -------------------------------------------------------------------------------- /examples/album/pages/album/index.js: -------------------------------------------------------------------------------- 1 | import { getAlbum } from '../../utils/store' 2 | 3 | Page({ 4 | data: { 5 | imageWidth: 0, 6 | imageMargin: 12, // 图片间距 7 | lineLimit: 3, // 每行多少张图片 8 | list: [], 9 | }, 10 | 11 | onLoad() { 12 | const { imageMargin, lineLimit } = this.data 13 | const { screenWidth } = wx.getSystemInfoSync() 14 | this.setData({ 15 | imageWidth: (screenWidth - imageMargin * 4) / lineLimit, // 图片宽度 16 | list: getAlbum(), 17 | }) 18 | }, 19 | }) -------------------------------------------------------------------------------- /examples/album/pages/album/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "navigation-bar": "../../components/navigation-bar/index", 4 | "album": "../../components/album/index" 5 | }, 6 | "disableScroll": true, 7 | "navigationStyle": "custom" 8 | } -------------------------------------------------------------------------------- /examples/album/pages/album/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/album/pages/album/index.wxss: -------------------------------------------------------------------------------- 1 | .cnt { 2 | position: absolute; 3 | top: 0; 4 | width: 100%; 5 | height: 100%; 6 | display: flex; 7 | flex-direction: column; 8 | } 9 | 10 | .album { 11 | height: 0; 12 | flex: 1; 13 | margin-bottom: 12px; 14 | } 15 | 16 | .safe-area-inset-bottom { 17 | height: env(safe-area-inset-bottom); 18 | } 19 | -------------------------------------------------------------------------------- /examples/album/pages/preview/index.js: -------------------------------------------------------------------------------- 1 | import { getAlbum } from '../../utils/store' 2 | 3 | Component({ 4 | properties: { 5 | imageid: { 6 | type: String, 7 | value: '', 8 | }, 9 | sourcepageid: { 10 | type: String, 11 | value: '', 12 | }, 13 | }, 14 | 15 | data: { 16 | imageId: '', 17 | sourcePageId: '', 18 | list: [], 19 | }, 20 | 21 | lifetimes: { 22 | attached() { 23 | // 因为页面的 onLoad 太迟,所以选用 component 构造器的 attached 生命周期来设置 shareKey,确保 share-element 动画正常执行 24 | const query = this.data 25 | const imageId = decodeURIComponent(query.imageid || '') 26 | const sourcePageId = decodeURIComponent(query.sourcepageid || '') 27 | const list = getAlbum() 28 | this.setData({ imageId, sourcePageId, list }) 29 | }, 30 | }, 31 | }) 32 | -------------------------------------------------------------------------------- /examples/album/pages/preview/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "previewer": "../../components/previewer/index" 4 | }, 5 | "navigationStyle": "custom", 6 | "backgroundColorContent": "#00000000", 7 | "disableScroll": true, 8 | "componentFramework": "glass-easel", 9 | "renderer": "skyline" 10 | } 11 | -------------------------------------------------------------------------------- /examples/album/pages/preview/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /examples/album/pages/preview/index.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | background-color: transparent; 3 | } 4 | 5 | .box-cotainer { 6 | width: 100vw; 7 | height: 100vh; 8 | position: relative; 9 | } 10 | 11 | .previewer { 12 | width: 100%; 13 | height: 100%; 14 | } 15 | -------------------------------------------------------------------------------- /examples/album/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件", 3 | "packOptions": { 4 | "ignore": [], 5 | "include": [] 6 | }, 7 | "setting": { 8 | "bundle": false, 9 | "userConfirmedBundleSwitch": false, 10 | "urlCheck": true, 11 | "scopeDataCheck": false, 12 | "coverView": true, 13 | "es6": true, 14 | "postcss": true, 15 | "compileHotReLoad": false, 16 | "lazyloadPlaceholderEnable": false, 17 | "preloadBackgroundData": false, 18 | "minified": true, 19 | "autoAudits": false, 20 | "newFeature": false, 21 | "uglifyFileName": false, 22 | "uploadWithSourceMap": true, 23 | "useIsolateContext": true, 24 | "nodeModules": false, 25 | "enhance": true, 26 | "useMultiFrameRuntime": true, 27 | "useApiHook": true, 28 | "useApiHostProcess": true, 29 | "showShadowRootInWxmlPanel": true, 30 | "packNpmManually": false, 31 | "enableEngineNative": false, 32 | "packNpmRelationList": [], 33 | "minifyWXSS": true, 34 | "showES6CompileOption": false, 35 | "minifyWXML": true, 36 | "babelSetting": { 37 | "ignore": [], 38 | "disablePlugins": [], 39 | "outputPath": "" 40 | }, 41 | "condition": false, 42 | "skylineRenderEnable": false, 43 | "compileWorklet": true 44 | }, 45 | "compileType": "miniprogram", 46 | "libVersion": "latest", 47 | "appid": "wxe5f52902cf4de896", 48 | "projectname": "album-demo", 49 | "condition": {}, 50 | "editorSetting": { 51 | "tabIndent": "insertSpaces", 52 | "tabSize": 2 53 | } 54 | } -------------------------------------------------------------------------------- /examples/album/project.private.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", 3 | "projectname": "album", 4 | "setting": { 5 | "compileHotReLoad": false, 6 | "skylineRenderEnable": true 7 | }, 8 | "libVersion": "latest" 9 | } -------------------------------------------------------------------------------- /examples/album/resources/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/awesome-skyline/25e2d93c2daf3f78d0908ff53ea305147d0c8978/examples/album/resources/back.png -------------------------------------------------------------------------------- /examples/album/sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /examples/album/utils/event-bus.js: -------------------------------------------------------------------------------- 1 | import { getRunOnUI } from './worklet' 2 | 3 | const map = {} 4 | 5 | function on(eventName, handler) { 6 | map[eventName] = map[eventName] || [] 7 | map[eventName].push(handler) 8 | } 9 | 10 | function off(eventName, handler) { 11 | const handlerList = map[eventName] 12 | if (!handlerList || !handlerList.length) return 13 | 14 | const index = handlerList.indexOf(handler) 15 | if (index !== -1) handlerList.splice(index, 1) 16 | } 17 | 18 | function emit(eventName, ...args) { 19 | const handlerList = map[eventName] 20 | if (!handlerList || !handlerList.length) return 21 | 22 | for (let i = handlerList.length - 1; i >= 0; i--) { 23 | handlerList[i](...args) 24 | } 25 | } 26 | 27 | function initWorkletEventBus(renderer) { 28 | getRunOnUI(renderer)(() => { 29 | 'worklet' 30 | if (!globalThis.temp) globalThis.temp = {} 31 | if (!globalThis.eventBus) { 32 | const eventBus = { 33 | map: {}, 34 | on(eventName, handler) { 35 | eventBus.map[eventName] = eventBus.map[eventName] || [] 36 | eventBus.map[eventName].push(handler) 37 | }, 38 | off(eventName, handler) { 39 | const handlerList = eventBus.map[eventName] 40 | if (!handlerList || !handlerList.length) return 41 | 42 | const index = handlerList.indexOf(handler) 43 | if (index !== -1) handlerList.splice(index, 1) 44 | }, 45 | emit(eventName, args) { 46 | const handlerList = eventBus.map[eventName] 47 | if (!handlerList || !handlerList.length) return 48 | 49 | for (let i = handlerList.length - 1; i >= 0; i--) { 50 | handlerList[i](args) 51 | } 52 | }, 53 | } 54 | globalThis.eventBus = eventBus 55 | } 56 | })() 57 | } 58 | 59 | export default { 60 | on, 61 | off, 62 | emit, 63 | initWorkletEventBus, 64 | } 65 | -------------------------------------------------------------------------------- /examples/album/utils/worklet.js: -------------------------------------------------------------------------------- 1 | export function getShared(renderer) { 2 | if (renderer === 'skyline') return wx.worklet.shared 3 | else return val => ({ value: val }) 4 | } 5 | 6 | export function getTiming(renderer) { 7 | 'worklet' 8 | if (renderer === 'skyline') return wx.worklet.timing 9 | else return (target, options, callback) => { 10 | if (typeof callback === 'function') setTimeout(callback, 0) 11 | return target 12 | } 13 | } 14 | 15 | export function getRunOnUI(renderer) { 16 | if (renderer === 'skyline') return wx.worklet.runOnUI 17 | else return func => func 18 | } 19 | 20 | export function getRunOnJS(renderer) { 21 | 'worklet' 22 | if (renderer === 'skyline') return wx.worklet.runOnJS 23 | else return func => func 24 | } 25 | -------------------------------------------------------------------------------- /examples/app-bar/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Eslint config file 3 | * Documentation: https://eslint.org/docs/user-guide/configuring/ 4 | * Install the Eslint extension before using this feature. 5 | */ 6 | module.exports = { 7 | env: { 8 | es6: true, 9 | browser: true, 10 | node: true, 11 | }, 12 | ecmaFeatures: { 13 | modules: true, 14 | }, 15 | parserOptions: { 16 | ecmaVersion: 2018, 17 | sourceType: 'module', 18 | }, 19 | globals: { 20 | wx: true, 21 | App: true, 22 | Page: true, 23 | getCurrentPages: true, 24 | getApp: true, 25 | Component: true, 26 | requirePlugin: true, 27 | requireMiniProgram: true, 28 | }, 29 | // extends: 'eslint:recommended', 30 | rules: {}, 31 | } 32 | -------------------------------------------------------------------------------- /examples/app-bar/app-bar/index.js: -------------------------------------------------------------------------------- 1 | // components/app-bar/index.js 2 | 3 | const { shared, timing, Easing } = wx.worklet 4 | 5 | export const GestureState = { 6 | POSSIBLE: 0, 7 | BEGIN: 1, 8 | ACTIVE: 2, 9 | END: 3, 10 | CANCELLED: 4, 11 | } 12 | 13 | export const lerp = function (begin, end, t) { 14 | 'worklet' 15 | return begin + (end - begin) * t 16 | } 17 | 18 | export const clamp = function (cur, lowerBound, upperBound) { 19 | 'worklet' 20 | if (cur > upperBound) return upperBound 21 | if (cur < lowerBound) return lowerBound 22 | return cur 23 | } 24 | 25 | const systemInfo = wx.getSystemInfoSync() 26 | const { statusBarHeight, screenHeight, screenWidth, safeArea } = systemInfo 27 | console.info('@@@ systemInfo', systemInfo) 28 | Component({ 29 | properties: { 30 | 31 | }, 32 | 33 | data: { 34 | maxCoverSize: 0, 35 | statusBarHeight: 0, 36 | musicCover: 'https://res.wx.qq.com/op_res/Nu9XXzXcXnD1j5EgWQ2ElxNcl1yMvnKypRo4MTbjOv7FC3saigGoOBTZibyESC7EXaClnPYhB6pvfb-IRmso6g' 37 | }, 38 | 39 | lifetimes: { 40 | attached() { 41 | const progress = shared(0) 42 | const initCoverSize = 60 // 初始图片大小 43 | const pagePadding = 24 44 | const maxCoverSize = screenWidth - 2 * pagePadding 45 | const safeAreaInsetBottom = screenHeight - safeArea.bottom 46 | const isIOS = systemInfo.system.indexOf('iOS') >= 0 47 | this.setData({ statusBarHeight, maxCoverSize }) 48 | 49 | this.applyAnimatedStyle('.cover', () => { 50 | 'worklet' 51 | const height = initCoverSize + (maxCoverSize - initCoverSize) * progress.value 52 | return { 53 | width: `${height}px`, 54 | height:`${height}px`, 55 | } 56 | }) 57 | 58 | this.applyAnimatedStyle('.expand-container', () => { 59 | 'worklet' 60 | const t = progress.value 61 | const maxRadius = 30 62 | const radius = isIOS ? maxRadius * t : 0 63 | const initBarHeight = initCoverSize + 8 * 2 + safeAreaInsetBottom 64 | return { 65 | top: `${(screenHeight - initBarHeight) * (1 - t)}px`, 66 | borderRadius: `${radius}px ${radius}px 0px 0px` 67 | } 68 | }) 69 | 70 | this.applyAnimatedStyle('.title-wrap', () => { 71 | 'worklet' 72 | return { 73 | opacity: 1 - progress.value 74 | } 75 | }) 76 | 77 | const navBarHeight = statusBarHeight + (isIOS ? 40 : 44) 78 | this.applyAnimatedStyle('.nav-bar', () => { 79 | 'worklet' 80 | const t = progress.value 81 | const threshold = 0.8 82 | const opacity = t < threshold ? 0 : (t - threshold) / (1 - threshold) 83 | 84 | return { 85 | opacity, 86 | height: `${navBarHeight * progress.value}px` 87 | } 88 | }) 89 | 90 | this.progress = progress 91 | } 92 | }, 93 | 94 | methods: { 95 | close() { 96 | this.progress.value = timing(0, { 97 | duration: 250, 98 | easing: Easing.ease 99 | }) 100 | }, 101 | 102 | expand() { 103 | this.progress.value = timing(1, { 104 | duration: 250, 105 | easing: Easing.ease 106 | }) 107 | }, 108 | 109 | handleDragUpdate(delta) { 110 | 'worklet' 111 | const curValue = this.progress.value 112 | const newVal = curValue - delta 113 | this.progress.value = clamp(newVal, 0.0, 1.0) 114 | }, 115 | 116 | handleDragEnd(velocity) { 117 | 'worklet' 118 | const t = this.progress.value 119 | let animateForward = false 120 | if (Math.abs(velocity) >= 1) { 121 | animateForward = velocity <= 0 122 | } else { 123 | animateForward = t > 0.7 124 | } 125 | const animationCurve = Easing.out(Easing.ease) 126 | if (animateForward) { 127 | this.progress.value = timing( 128 | 1.0, { 129 | duration: 200, 130 | easing: animationCurve, 131 | }) 132 | } else { 133 | this.progress.value = timing( 134 | 0.0, { 135 | duration: 250, 136 | easing: animationCurve, 137 | }) 138 | } 139 | }, 140 | 141 | handleVerticalDrag(evt) { 142 | 'worklet' 143 | if (evt.state === GestureState.ACTIVE) { 144 | const delta = evt.deltaY / screenHeight 145 | this.handleDragUpdate(delta) 146 | } else if (evt.state === GestureState.END) { 147 | const velocity = evt.velocityY / screenHeight 148 | this.handleDragEnd(velocity) 149 | } else if (evt.state === GestureState.CANCELLED) { 150 | this.handleDragEnd(0.0) 151 | } 152 | }, 153 | }, 154 | 155 | }) -------------------------------------------------------------------------------- /examples/app-bar/app-bar/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /examples/app-bar/app-bar/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Skyline 渲染框架入门与实践 24 | 小程序技术专员 - binnie 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 微信学堂 37 | 88 人在学 38 | 39 | 40 | 41 | 42 | Skyline 渲染框架入门与实践 43 | 小程序技术专员 - binnie 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /examples/app-bar/app-bar/index.wxss: -------------------------------------------------------------------------------- 1 | .expand-container { 2 | position: absolute; 3 | top: 0; 4 | right: 0; 5 | bottom: 0; 6 | left: 0; 7 | padding: 0 24px; 8 | padding-bottom: env(safe-area-inset-bottom); 9 | pointer-events: auto; 10 | overflow: hidden; 11 | background-color: #e9f8f7; 12 | color: #7e8081; 13 | } 14 | 15 | .hide { 16 | display: none; 17 | } 18 | 19 | .nav-bar { 20 | overflow: hidden; 21 | box-sizing: border-box; 22 | } 23 | 24 | .icon--back { 25 | width: 30px; 26 | height: 30px; 27 | } 28 | 29 | .title { 30 | margin-left: 10px; 31 | min-width: 160px; 32 | flex: 1; 33 | } 34 | .title .name { 35 | margin-top: 4px; 36 | font-size: 12px; 37 | } 38 | 39 | .title-wrap { 40 | flex: 1; 41 | min-width: 240px; 42 | } 43 | 44 | .footer { 45 | padding: 0 24px; 46 | } 47 | 48 | .footer .icon { 49 | width: 40px; 50 | height: 40px; 51 | } 52 | 53 | .expand-cover { 54 | width: 100%; 55 | height: 100%; 56 | } 57 | 58 | .music-title { 59 | margin-top: 48px; 60 | font-size: 20px; 61 | font-weight: bold; 62 | color: #07c160; 63 | } 64 | .music-title .name { 65 | margin-top: 12px; 66 | font-size: 14px; 67 | font-weight: 200; 68 | color: #b1b2b3; 69 | } 70 | 71 | .cover-area { 72 | margin: 8px 0; 73 | width: 100%; 74 | aspect-ratio: 1 / 1; 75 | overflow: hidden; 76 | } 77 | 78 | .cover { 79 | /* aspect-ratio: 1 / 1; */ 80 | flex-shrink: 0; 81 | } 82 | 83 | .icon { 84 | width: 18px; 85 | height: 18px; 86 | } 87 | 88 | .row { 89 | display: flex; 90 | flex-direction: row; 91 | align-items: center; 92 | } 93 | 94 | .row-between { 95 | display: flex; 96 | flex-direction: row; 97 | justify-content: space-between; 98 | align-items: center; 99 | } 100 | 101 | .column { 102 | display: flex; 103 | flex-direction: column; 104 | } 105 | 106 | .column-main-center { 107 | display: flex; 108 | flex-direction: column; 109 | justify-content: center; 110 | } 111 | 112 | .column-cross-center { 113 | display: flex; 114 | flex-direction: column; 115 | align-items: center; 116 | } 117 | 118 | .center { 119 | display: flex; 120 | align-items: center; 121 | justify-content: center; 122 | } 123 | 124 | .circle { 125 | width: 100%; 126 | height: 100%; 127 | overflow: hidden; 128 | border-radius: 50%; 129 | } -------------------------------------------------------------------------------- /examples/app-bar/app.js: -------------------------------------------------------------------------------- 1 | // app.js 2 | App({}) 3 | -------------------------------------------------------------------------------- /examples/app-bar/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "pages/detail/index" 5 | ], 6 | "window": { 7 | "navigationBarTextStyle": "black", 8 | "navigationStyle": "custom" 9 | }, 10 | "style": "v2", 11 | "renderer": "skyline", 12 | "appBar": {}, 13 | "rendererOptions": { 14 | "skyline": { 15 | "defaultDisplayBlock": true, 16 | "defaultContentBox": true, 17 | "disableABTest": true, 18 | "sdkVersionBegin": "3.0.0", 19 | "sdkVersionEnd": "15.255.255" 20 | } 21 | }, 22 | "componentFramework": "glass-easel", 23 | "sitemapLocation": "sitemap.json", 24 | "lazyCodeLoading": "requiredComponents" 25 | } -------------------------------------------------------------------------------- /examples/app-bar/app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | .container { 3 | height: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: space-between; 8 | padding: 200rpx 0; 9 | box-sizing: border-box; 10 | } 11 | -------------------------------------------------------------------------------- /examples/app-bar/assets/arrow-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/awesome-skyline/25e2d93c2daf3f78d0908ff53ea305147d0c8978/examples/app-bar/assets/arrow-down.png -------------------------------------------------------------------------------- /examples/app-bar/assets/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/awesome-skyline/25e2d93c2daf3f78d0908ff53ea305147d0c8978/examples/app-bar/assets/next.png -------------------------------------------------------------------------------- /examples/app-bar/assets/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/awesome-skyline/25e2d93c2daf3f78d0908ff53ea305147d0c8978/examples/app-bar/assets/play.png -------------------------------------------------------------------------------- /examples/app-bar/components/navigation-bar/navigation-bar.js: -------------------------------------------------------------------------------- 1 | Component({ 2 | options: { 3 | multipleSlots: true // 在组件定义时的选项中启用多slot支持 4 | }, 5 | /** 6 | * 组件的属性列表 7 | */ 8 | properties: { 9 | extClass: { 10 | type: String, 11 | value: '' 12 | }, 13 | title: { 14 | type: String, 15 | value: '' 16 | }, 17 | background: { 18 | type: String, 19 | value: '' 20 | }, 21 | color: { 22 | type: String, 23 | value: '' 24 | }, 25 | back: { 26 | type: Boolean, 27 | value: true 28 | }, 29 | loading: { 30 | type: Boolean, 31 | value: false 32 | }, 33 | animated: { 34 | // 显示隐藏的时候opacity动画效果 35 | type: Boolean, 36 | value: true 37 | }, 38 | show: { 39 | // 显示隐藏导航,隐藏的时候navigation-bar的高度占位还在 40 | type: Boolean, 41 | value: true, 42 | observer: '_showChange' 43 | }, 44 | // back为true的时候,返回的页面深度 45 | delta: { 46 | type: Number, 47 | value: 1 48 | } 49 | }, 50 | /** 51 | * 组件的初始数据 52 | */ 53 | data: { 54 | displayStyle: '' 55 | }, 56 | attached() { 57 | const isSupport = !!wx.getMenuButtonBoundingClientRect 58 | const rect = wx.getMenuButtonBoundingClientRect 59 | ? wx.getMenuButtonBoundingClientRect() 60 | : null 61 | wx.getSystemInfo({ 62 | success: (res) => { 63 | const ios = !!(res.system.toLowerCase().search('ios') + 1) 64 | this.setData({ 65 | ios, 66 | statusBarHeight: res.statusBarHeight, 67 | navBarHeight: rect.bottom - rect.top + 10, 68 | innerWidth: isSupport ? `width:${rect.left}px` : '', 69 | innerPaddingRight: isSupport 70 | ? `padding-right:${res.windowWidth - rect.left}px` 71 | : '', 72 | leftWidth: isSupport ? `width:${res.windowWidth - rect.left}px` : '', 73 | theme: res.theme || 'light', 74 | }) 75 | } 76 | }) 77 | 78 | if (wx.onThemeChange) { 79 | wx.onThemeChange(({theme}) => { 80 | this.setData({theme}) 81 | }) 82 | } 83 | }, 84 | detached() { 85 | if (wx.offThemeChange) { 86 | wx.offThemeChange() 87 | } 88 | }, 89 | /** 90 | * 组件的方法列表 91 | */ 92 | methods: { 93 | _showChange(show) { 94 | const animated = this.data.animated 95 | let displayStyle = '' 96 | if (animated) { 97 | displayStyle = `opacity: ${ 98 | show ? '1' : '0' 99 | };-webkit-transition:opacity 0.5s;transition:opacity 0.5s;` 100 | } else { 101 | displayStyle = `display: ${show ? '' : 'none'}` 102 | } 103 | this.setData({ 104 | displayStyle 105 | }) 106 | }, 107 | back() { 108 | const data = this.data 109 | console.log('---------222',getCurrentPages().length) 110 | if (data.delta) { 111 | wx.navigateBack({ 112 | delta: data.delta 113 | }) 114 | } 115 | // 如果是直接打开的,就默认回首页 116 | if (getCurrentPages().length == 1) { 117 | console.log('---------333') 118 | wx.switchTab({ 119 | url: '/page/component/index', 120 | complete: (res) => { 121 | console.log(res) 122 | } 123 | }) 124 | } 125 | this.triggerEvent('back', { delta: data.delta }, {}) 126 | } 127 | } 128 | }) 129 | -------------------------------------------------------------------------------- /examples/app-bar/components/navigation-bar/navigation-bar.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {}, 4 | "componentFramework": "glass-easel", 5 | "renderer": "skyline", 6 | "styleIsolation": "apply-shared" 7 | } 8 | -------------------------------------------------------------------------------- /examples/app-bar/components/navigation-bar/navigation-bar.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 31 | 32 | {{title}} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/app-bar/components/navigation-bar/navigation-bar.wxss: -------------------------------------------------------------------------------- 1 | .weui-navigation-bar { 2 | display: flex; 3 | overflow: hidden; 4 | color: rgba(0, 0, 0, .9); 5 | width: 100vw; 6 | } 7 | 8 | .weui-navigation-bar__placeholder { 9 | background: #f7f7f7; 10 | position: relative; 11 | } 12 | 13 | .weui-navigation-bar__inner, .weui-navigation-bar__inner .weui-navigation-bar__left { 14 | display: flex; 15 | align-items: center; 16 | flex-direction: row; 17 | } 18 | 19 | .weui-navigation-bar__inner { 20 | position: relative; 21 | padding-right: 95px; 22 | width: 100vw; 23 | } 24 | 25 | .weui-navigation-bar__inner .weui-navigation-bar__left { 26 | position: relative; 27 | width: 95px; 28 | padding-left: 16px; 29 | } 30 | 31 | .weui-navigation-bar__btn_goback_wrapper { 32 | padding: 11px 18px 11px 16px; 33 | margin: -11px -18px -11px -16px; 34 | } 35 | 36 | .weui-navigation-bar__inner .weui-navigation-bar__left .weui-navigation-bar__btn_goback { 37 | font-size: 12px; 38 | width: 12px; 39 | height: 24px; 40 | background: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z'/%3E%3C/svg%3E") no-repeat 50% 50%; 41 | background-color: currentColor; 42 | background-size: cover; 43 | } 44 | 45 | .weui-navigation-bar__inner .weui-navigation-bar__center { 46 | font-size: 17px; 47 | text-align: center; 48 | position: relative; 49 | flex: 1; 50 | display: flex; 51 | align-items: center; 52 | justify-content: center; 53 | font-weight: bold; 54 | } 55 | 56 | [data-weui-theme=dark].weui-navigation-bar { 57 | color: hsla(0, 0%, 100%, .8); 58 | } 59 | [data-weui-theme=dark] .weui-navigation-bar__inner { 60 | background-color: #1f1f1f; 61 | } 62 | -------------------------------------------------------------------------------- /examples/app-bar/pages/detail/index.js: -------------------------------------------------------------------------------- 1 | // pages/detail/index.js 2 | const musicList = [ 3 | { 4 | id: 0, 5 | coverImg: 'https://res.wx.qq.com/op_res/Nu9XXzXcXnD1j5EgWQ2ElxNcl1yMvnKypRo4MTbjOv7FC3saigGoOBTZibyESC7EXaClnPYhB6pvfb-IRmso6g', 6 | title: 'Skyline 渲染框架' 7 | }, 8 | { 9 | id: 1, 10 | coverImg: 'https://res.wx.qq.com/op_res/Nu9XXzXcXnD1j5EgWQ2El3JJ3FgQX_YP9sI6kJD_nLjnkdN19yZ5nLtS3cqtNUx621vrni0Kjy5uoX_QMlBJgQ', 11 | title: '小程序性能优化' 12 | }, 13 | { 14 | id: 2, 15 | coverImg: 'https://res.wx.qq.com/op_res/Nu9XXzXcXnD1j5EgWQ2ElwWbBogi5f0NNRBkuJWfE8HQzysKxBaoCJ-YBr7irwn_uE37dHQTWcHK2uOHIWsQ3Q', 16 | title: '医疗行业实践' 17 | }, 18 | 19 | ] 20 | Page({ 21 | data: { 22 | music: musicList[0], 23 | albumMusicList: [ 24 | { 25 | id: 0, 26 | coverImg: 'https://res.wx.qq.com/op_res/Nu9XXzXcXnD1j5EgWQ2ElxNcl1yMvnKypRo4MTbjOv7FC3saigGoOBTZibyESC7EXaClnPYhB6pvfb-IRmso6g', 27 | name: 'Skyline 渲染框架', 28 | author: '小程序技术专员 - binnie' 29 | }, 30 | { 31 | id: 1, 32 | coverImg: 'https://res.wx.qq.com/op_res/Nu9XXzXcXnD1j5EgWQ2El3JJ3FgQX_YP9sI6kJD_nLjnkdN19yZ5nLtS3cqtNUx621vrni0Kjy5uoX_QMlBJgQ', 33 | name: '小程序性能优化', 34 | author: '小程序性能优化专家' 35 | }, 36 | { 37 | id: 2, 38 | coverImg: 'https://res.wx.qq.com/op_res/Nu9XXzXcXnD1j5EgWQ2ElwWbBogi5f0NNRBkuJWfE8HQzysKxBaoCJ-YBr7irwn_uE37dHQTWcHK2uOHIWsQ3Q', 39 | name: '医疗行业实践', 40 | author: '小程序医疗行业专家' 41 | }, 42 | ] 43 | }, 44 | 45 | onLoad(query) { 46 | const idx = query.idx 47 | if (idx) { 48 | this.setData({ 49 | music: musicList[idx] 50 | }) 51 | } 52 | }, 53 | 54 | onReady() { 55 | 56 | }, 57 | 58 | 59 | onShow() { 60 | 61 | }, 62 | }) -------------------------------------------------------------------------------- /examples/app-bar/pages/detail/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "navigation-bar": "../../components/navigation-bar/navigation-bar" 4 | } 5 | } -------------------------------------------------------------------------------- /examples/app-bar/pages/detail/index.wxml: -------------------------------------------------------------------------------- 1 | 7 | 13 | 14 | 15 | 16 | 17 | 为你推荐 18 | 19 | 20 | 21 | {{item.name}} 22 | {{item.author}} 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/app-bar/pages/detail/index.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100vh; 5 | } 6 | 7 | .scroll-area { 8 | flex: 1; 9 | overflow-y: hidden; 10 | padding: 0 24px; 11 | box-sizing: border-box; 12 | margin-bottom: calc(84px + env(safe-area-inset-bottom)); 13 | } 14 | 15 | .intro { 16 | padding: 30px; 17 | text-align: center; 18 | } 19 | 20 | .cover { 21 | width: 250px; 22 | height: 250px; 23 | } 24 | 25 | .album-music { 26 | padding: 16px 0; 27 | border-top: 0.5px solid #f1e9e9; 28 | } 29 | 30 | .album-music-cover { 31 | width: 48px; 32 | height: 48px; 33 | margin-right: 16px; 34 | } 35 | 36 | .row { 37 | display: flex; 38 | flex-direction: row; 39 | } 40 | 41 | .row-between { 42 | display: flex; 43 | flex-direction: row; 44 | justify-content: space-between; 45 | align-items: center; 46 | } 47 | 48 | .column { 49 | display: flex; 50 | flex-direction: column; 51 | } 52 | 53 | .column-main-center { 54 | display: flex; 55 | flex-direction: column; 56 | justify-content: center; 57 | } 58 | 59 | .column-cross-center { 60 | display: flex; 61 | flex-direction: column; 62 | align-items: center; 63 | } 64 | 65 | .center { 66 | display: flex; 67 | align-items: center; 68 | justify-content: center; 69 | } 70 | 71 | .circle { 72 | width: 100%; 73 | height: 100%; 74 | overflow: hidden; 75 | border-radius: 50%; 76 | } 77 | .author { 78 | font-size: 12px; 79 | color: #7e8081; 80 | margin-top: 6px; 81 | } 82 | -------------------------------------------------------------------------------- /examples/app-bar/pages/index/index.js: -------------------------------------------------------------------------------- 1 | const app = getApp() 2 | 3 | Page({ 4 | data: { 5 | back: false, 6 | maxCoverSize: 0, 7 | musicList: [ 8 | { 9 | id: 0, 10 | coverImg: 'https://res.wx.qq.com/op_res/Nu9XXzXcXnD1j5EgWQ2ElxNcl1yMvnKypRo4MTbjOv7FC3saigGoOBTZibyESC7EXaClnPYhB6pvfb-IRmso6g', 11 | title: 'Skyline 渲染框架' 12 | }, 13 | { 14 | id: 1, 15 | coverImg: 'https://res.wx.qq.com/op_res/Nu9XXzXcXnD1j5EgWQ2El3JJ3FgQX_YP9sI6kJD_nLjnkdN19yZ5nLtS3cqtNUx621vrni0Kjy5uoX_QMlBJgQ', 16 | title: '小程序性能优化' 17 | }, 18 | { 19 | id: 2, 20 | coverImg: 'https://res.wx.qq.com/op_res/Nu9XXzXcXnD1j5EgWQ2ElwWbBogi5f0NNRBkuJWfE8HQzysKxBaoCJ-YBr7irwn_uE37dHQTWcHK2uOHIWsQ3Q', 21 | title: '医疗行业实践' 22 | }, 23 | 24 | ] 25 | }, 26 | onLoad() { 27 | // 在小程序示例中展示返回按钮 28 | if (this.route.includes('packageSkylineExamples/examples/')) { 29 | this.setData({ 30 | back: true 31 | }) 32 | } 33 | }, 34 | onShow() { 35 | // 仅在 app-bar demo 页面展示 36 | if (typeof this.getAppBar === 'function' ) { 37 | const appBarComp = this.getAppBar() 38 | // component.getAppBar 在 Skyline 中返回 appBar 组件实例,在 webview 中返回 null 39 | if (appBarComp !== null) { 40 | appBarComp.setData({ 41 | showAppbar: true 42 | }) 43 | } 44 | } 45 | }, 46 | 47 | goDetail(e) { 48 | const idx = e.currentTarget.dataset.idx 49 | wx.navigateTo({ 50 | url: `../detail/index?idx=${idx}`, 51 | }) 52 | } 53 | }) 54 | -------------------------------------------------------------------------------- /examples/app-bar/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "navigation-bar": "../../components/navigation-bar/navigation-bar" 4 | } 5 | } -------------------------------------------------------------------------------- /examples/app-bar/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 7 | 13 | 本月课程上新 14 | 15 | 22 | 23 | {{item.title}} 24 | 25 | 26 | 最近学过 27 | 28 | 35 | 36 | {{item.title}} 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/app-bar/pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100vh; 5 | } 6 | 7 | .scroll-area { 8 | flex: 1; 9 | overflow-y: hidden; 10 | padding: 0 8px; 11 | box-sizing: border-box; 12 | margin-bottom: calc(84px + env(safe-area-inset-bottom)); 13 | } 14 | 15 | .intro { 16 | padding: 30px; 17 | text-align: center; 18 | } 19 | 20 | .app-bar { 21 | position: absolute; 22 | width: 100vw; 23 | height: 100vh; 24 | pointer-events: none; 25 | } 26 | 27 | .title { 28 | margin-top: 16px; 29 | margin-left: 8px; 30 | font-size: 20px; 31 | font-weight: 600; 32 | } 33 | 34 | .cards { 35 | /* margin: 24px 0; */ 36 | display: flex; 37 | flex-direction: row; 38 | flex-wrap: wrap; 39 | } 40 | 41 | .card { 42 | display: flex; 43 | justify-content: center; 44 | flex-direction: column; 45 | margin: 8px; 46 | } 47 | 48 | .cover { 49 | width: 43vw; 50 | height: 43vw; 51 | margin-bottom: 10px; 52 | } -------------------------------------------------------------------------------- /examples/app-bar/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appid": "wxe5f52902cf4de896", 3 | "compileType": "miniprogram", 4 | "libVersion": "3.3.2", 5 | "packOptions": { 6 | "ignore": [], 7 | "include": [] 8 | }, 9 | "setting": { 10 | "coverView": true, 11 | "es6": true, 12 | "postcss": true, 13 | "minified": true, 14 | "enhance": true, 15 | "showShadowRootInWxmlPanel": true, 16 | "packNpmRelationList": [], 17 | "babelSetting": { 18 | "ignore": [], 19 | "disablePlugins": [], 20 | "outputPath": "" 21 | }, 22 | "compileWorklet": true 23 | }, 24 | "condition": {}, 25 | "editorSetting": { 26 | "tabIndent": "auto", 27 | "tabSize": 4 28 | } 29 | } -------------------------------------------------------------------------------- /examples/app-bar/project.private.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", 3 | "projectname": "app-bar", 4 | "setting": { 5 | "compileHotReLoad": true, 6 | "skylineRenderEnable": true 7 | }, 8 | "libVersion": "3.3.3" 9 | } -------------------------------------------------------------------------------- /examples/app-bar/sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /examples/associated-scroll-view/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Eslint config file 3 | * Documentation: https://eslint.org/docs/user-guide/configuring/ 4 | * Install the Eslint extension before using this feature. 5 | */ 6 | module.exports = { 7 | env: { 8 | es6: true, 9 | browser: true, 10 | node: true, 11 | }, 12 | ecmaFeatures: { 13 | modules: true, 14 | }, 15 | parserOptions: { 16 | ecmaVersion: 2018, 17 | sourceType: 'module', 18 | }, 19 | globals: { 20 | wx: true, 21 | App: true, 22 | Page: true, 23 | getCurrentPages: true, 24 | getApp: true, 25 | Component: true, 26 | requirePlugin: true, 27 | requireMiniProgram: true, 28 | }, 29 | // extends: 'eslint:recommended', 30 | rules: {}, 31 | } 32 | -------------------------------------------------------------------------------- /examples/associated-scroll-view/app.js: -------------------------------------------------------------------------------- 1 | // app.js 2 | App({}) 3 | -------------------------------------------------------------------------------- /examples/associated-scroll-view/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index" 4 | ], 5 | "window": { 6 | "backgroundTextStyle": "light", 7 | "navigationBarBackgroundColor": "#fff", 8 | "navigationBarTitleText": "Weixin", 9 | "navigationBarTextStyle": "black" 10 | }, 11 | "style": "v2", 12 | "lazyCodeLoading": "requiredComponents", 13 | "renderer": "skyline", 14 | "rendererOptions": { 15 | "skyline": { 16 | "defaultDisplayBlock": true, 17 | "disableABTest": true, 18 | "sdkVersionBegin": "3.0.0", 19 | "sdkVersionEnd": "15.255.255" 20 | } 21 | }, 22 | "componentFramework": "glass-easel", 23 | "sitemapLocation": "sitemap.json" 24 | } 25 | -------------------------------------------------------------------------------- /examples/associated-scroll-view/app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | .container { 3 | height: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: space-between; 8 | padding: 200rpx 0; 9 | box-sizing: border-box; 10 | } 11 | -------------------------------------------------------------------------------- /examples/associated-scroll-view/components/category-list/category-list.js: -------------------------------------------------------------------------------- 1 | const { shared } = wx.worklet 2 | 3 | const GestureState = { 4 | POSSIBLE: 0, // 0 此时手势未识别,如 panDown等 5 | BEGIN: 1, // 1 手势已识别 6 | ACTIVE: 2, // 2 连续手势活跃状态 7 | END: 3, // 3 手势终止 8 | CANCELLED: 4, // 4 手势取消, 9 | } 10 | 11 | Component({ 12 | options: { 13 | virtualHost: true, 14 | }, 15 | properties: { 16 | products: { 17 | type: Object, 18 | value: [], 19 | }, 20 | max: { 21 | type: Number, 22 | value: 0, 23 | }, 24 | index: { 25 | type: Number, 26 | value: 0, 27 | }, 28 | }, 29 | lifetimes: { 30 | created() { 31 | this._swiping = shared(false) 32 | this._canSwipe = shared(false) 33 | this._scrollTop = shared(0) 34 | this._scrollHeight = shared(100) 35 | this._height = shared(0) 36 | }, 37 | attached() { 38 | this.createSelectorQuery().select('.product-list').boundingClientRect(rect => { 39 | this._height.value = rect.height 40 | this._scrollHeight.value = rect.height + 1 // 可滚动高度总是比滚动区域高一点 41 | }).exec() 42 | }, 43 | }, 44 | methods: { 45 | getCanSwipe() { 46 | return this._canSwipe 47 | }, 48 | setSwipingValue(swiping) { 49 | this._swiping = swiping 50 | }, 51 | shouldScrollViewResp(e) { 52 | 'worklet' 53 | // 前者是判断到顶往下拉,后者反之 54 | this._canSwipe.value = this._scrollTop.value <= 0 && e.deltaY > 0 && this.properties.index !== 0 || 55 | this._scrollTop.value + this._height.value >= this._scrollHeight.value && e.deltaY < 0 && this.properties.index !== this.properties.max 56 | // 滑动 swiper 期间 scroll-view 不可滚动 57 | return !this._swiping.value && !this._canSwipe.value 58 | }, 59 | handleScroll(e) { 60 | 'worklet' 61 | this._scrollTop.value = e.detail.scrollTop 62 | this._scrollHeight.value = e.detail.scrollHeight 63 | }, 64 | }, 65 | }) 66 | -------------------------------------------------------------------------------- /examples/associated-scroll-view/components/category-list/category-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /examples/associated-scroll-view/components/category-list/category-list.wxml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | {{item.name}} 12 | {{item.comment}} 13 | {{item.sales}}人学过好评度100% 14 | 15 | ¥ 16 | {{item.price}} 17 | 18 | 19 | 20 | + 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/associated-scroll-view/components/category-list/category-list.wxss: -------------------------------------------------------------------------------- 1 | .product-list { 2 | height: 100%; 3 | } 4 | .product-item { 5 | display: flex; 6 | flex-direction: row; 7 | padding: 8px; 8 | } 9 | .product-item:first-child { 10 | padding-top: 16px; 11 | } 12 | .product-item:last-child { 13 | padding-bottom: 16px; 14 | border-bottom: 1px solid #F9F9F9; 15 | } 16 | .product-image { 17 | width: 133px; 18 | height: 100px; 19 | border-radius: 5px; 20 | } 21 | .product-info { 22 | flex: 1; 23 | padding: 0 8px; 24 | font-size: 10px; 25 | } 26 | .product-info view, .product-info text { 27 | margin-bottom: 3px; 28 | } 29 | .product-name { 30 | font-size: 16px; 31 | } 32 | .product-comment { 33 | display: flex; 34 | flex-direction: row; 35 | } 36 | .product-comment text { 37 | background-color: #EEE; 38 | border-radius: 3px; 39 | padding: 1px 3px; 40 | } 41 | .product-data { 42 | display: flex; 43 | flex-direction: row; 44 | color: gray; 45 | } 46 | .product-data text { 47 | margin-right: 20px; 48 | } 49 | .product-discount { 50 | display: flex; 51 | flex-direction: row; 52 | } 53 | .product-discount text { 54 | color: red; 55 | border: 0.3px solid red; 56 | padding: 0 3px; 57 | border-radius: 3px; 58 | } 59 | .product-price { 60 | color: red; 61 | vertical-align: baseline; 62 | } 63 | .product-add-to-cart { 64 | position: absolute; 65 | right: 12px; 66 | bottom: 12px; 67 | border-radius: 6px; 68 | color: #fff; 69 | background-color: #44b549; 70 | width: 20px; 71 | height: 20px; 72 | line-height: 16px; 73 | font-weight: 500; 74 | text-align: center; 75 | } 76 | -------------------------------------------------------------------------------- /examples/associated-scroll-view/pages/index/index.js: -------------------------------------------------------------------------------- 1 | import { getCategories, getProducts } from "../../util" 2 | 3 | const { shared } = wx.worklet 4 | 5 | Component({ 6 | data: { 7 | categories: getCategories(), 8 | selected: 0, 9 | products: getProducts(), 10 | }, 11 | lifetimes: { 12 | created() { 13 | this._swiping = shared(false) 14 | this._canSwipe = [] 15 | this._selected = shared(0) 16 | this._lastIndex = shared(0) 17 | }, 18 | attached() { 19 | this._canSwipe = this.selectAllComponents('.category-list').map(comp => { 20 | comp.setSwipingValue(this._swiping) 21 | return comp.getCanSwipe() 22 | }) 23 | }, 24 | }, 25 | methods: { 26 | shouldSwiperResp() { 27 | 'worklet' 28 | if (this._lastIndex.value !== this._selected.value) { 29 | this._lastIndex.value = this._selected.value 30 | // 每次切换 swiper item 时重置,优先给滚动 31 | this._canSwipe[this._selected.value].value = false 32 | } 33 | return this._canSwipe[this._selected.value].value 34 | }, 35 | onSwiperStart() { 36 | 'worklet' 37 | this._swiping.value = true 38 | }, 39 | onSwiperEnd() { 40 | 'worklet' 41 | this._swiping.value = false 42 | }, 43 | onChange(e) { 44 | const {current} = e.detail 45 | this.setData({ 46 | selected: current, 47 | }) 48 | this._selected.value = current 49 | wx.vibrateShort({ 50 | type: 'light', 51 | }) 52 | }, 53 | }, 54 | }) 55 | -------------------------------------------------------------------------------- /examples/associated-scroll-view/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "category-list": "../../components/category-list/category-list" 4 | }, 5 | "disableScroll": true, 6 | "navigationStyle": "custom" 7 | } -------------------------------------------------------------------------------- /examples/associated-scroll-view/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 请输入课程名称 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | {{item.name}} 19 | 20 | 21 | 22 | 23 | 24 | {{item.name}} 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /examples/associated-scroll-view/pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | view { 2 | position: relative; 3 | box-sizing: border-box; 4 | } 5 | page { 6 | background-color: #F9F9F9; 7 | display: flex; 8 | flex-direction: column; 9 | height: 100vh; 10 | } 11 | 12 | .navigation-bar { 13 | width: 100%; 14 | padding-top: calc(env(safe-area-inset-top) + 0.001px); 15 | } 16 | .navigation-bar-content { 17 | height: 44px; 18 | display: flex; 19 | flex-direction: row; 20 | justify-content: center; 21 | align-items: center; 22 | font-size: 20px; 23 | } 24 | .navigation-bar-content.white { 25 | color: white; 26 | } 27 | .navigation-bar-content.black { 28 | color: black; 29 | } 30 | .navigation-bar-content .back, .navigation-bar-content .more { 31 | width: 44px; 32 | text-align: center; 33 | } 34 | .navigation-bar-content .search { 35 | flex: 1; 36 | margin-right: 60px; 37 | margin-top: 7px; 38 | align-self: flex-start; 39 | } 40 | .navigation-bar-content .search-input { 41 | position: absolute; 42 | right: 0; 43 | width: 100%; 44 | height: 30px; 45 | line-height: 30px; 46 | padding-left: 20px; 47 | border: 0.5px solid #44b549; 48 | border-radius: 15px; 49 | font-size: 12px; 50 | background-color: #F8F8F8; 51 | color: #AAA; 52 | } 53 | 54 | .first-category { 55 | height: 100px; 56 | } 57 | .first-category-list { 58 | height: 100%; 59 | display: flex; 60 | flex-direction: row; 61 | } 62 | .first-category-item { 63 | padding: 10px; 64 | flex-shrink: 0; 65 | } 66 | .first-category-item-image { 67 | width: 50px; 68 | height: 50px; 69 | border-radius: 100%; 70 | border: 2px solid white; 71 | } 72 | .first-category-item-name { 73 | font-size: 12px; 74 | text-align: center; 75 | width: 50px; 76 | margin-top: 6px; 77 | } 78 | 79 | .main { 80 | flex: 1; 81 | display: flex; 82 | flex-direction: row; 83 | } 84 | .second-category { 85 | font-size: 12px; 86 | background-color: #F9F9F9; 87 | } 88 | .second-category-item { 89 | padding: 15px 5px; 90 | width: 80px; 91 | transition: ease .3s; 92 | transition-property: font-size; 93 | text-align: center; 94 | } 95 | .second-category-item.selected { 96 | background-color: white; 97 | font-size: 14px; 98 | font-weight: bold; 99 | color: #44b549; 100 | } 101 | 102 | .product-list-wrapper { 103 | flex: 1; 104 | background-color: white; 105 | height: auto; 106 | } 107 | -------------------------------------------------------------------------------- /examples/associated-scroll-view/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appid": "wxe5f52902cf4de896", 3 | "compileType": "miniprogram", 4 | "libVersion": "latest", 5 | "packOptions": { 6 | "ignore": [], 7 | "include": [] 8 | }, 9 | "setting": { 10 | "coverView": true, 11 | "es6": true, 12 | "postcss": true, 13 | "minified": true, 14 | "enhance": true, 15 | "showShadowRootInWxmlPanel": true, 16 | "packNpmRelationList": [], 17 | "babelSetting": { 18 | "ignore": [], 19 | "disablePlugins": [], 20 | "outputPath": "" 21 | }, 22 | "condition": false, 23 | "skylineRenderEnable": true, 24 | "compileWorklet": true 25 | }, 26 | "condition": {}, 27 | "editorSetting": { 28 | "tabIndent": "auto", 29 | "tabSize": 4 30 | } 31 | } -------------------------------------------------------------------------------- /examples/associated-scroll-view/project.private.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", 3 | "projectname": "associated-scroll-view", 4 | "setting": { 5 | "compileHotReLoad": false, 6 | "skylineRenderEnable": true 7 | }, 8 | "libVersion": "latest", 9 | "condition": {} 10 | } -------------------------------------------------------------------------------- /examples/associated-scroll-view/sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /examples/associated-scroll-view/util.js: -------------------------------------------------------------------------------- 1 | export function getCategories() { 2 | const categories = [ 3 | '中小商户', 4 | '商超零售', 5 | '品牌服饰', 6 | '餐饮', 7 | '医疗', 8 | '酒旅', 9 | '政务', 10 | '开发技术', 11 | '产品能力', 12 | '运营规范', 13 | ] 14 | const images = [ 15 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU_LhYxhaP5JTy7TWgezsDY7RW_l_e04fR7oG7sCKmS8hc8mVeZaY6eUWT3nk-ww_ZQ', 16 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU-O3axOjUJGFgutF9Xc1JL1uxXFWYdW85mWG0Zvm5nv7rvP18CJ0q6-RRFM0xWLLog', 17 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU3ywQmrV-rSREDwo0Hp9m7iIZZ7Njvjq_TlOg_0ss0cgQL0pfKOuB2NRpAcwfALxvw', 18 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU1GROxmiPIBOCoA5Es44GxjN0KuCQQsoxEH33l05TCgk04n0dssHAIPxIV2ycSlSJA', 19 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU68wmBQzYcfQfuIAh1IKWq7OyG0EWxdWGhotYHFh-k-JpmkJ1Otq-mYUT8Dp3iucvg', 20 | 'https://res.wx.qq.com/op_res/UxKgRAAdvQE0sTh7eCEwT1ASo_fbE3TppPoza_9I7U7OreGNv8F8ltq80gqQSzxa-86bt-gWalLtgPDAhflj-w', 21 | 'https://res.wx.qq.com/op_res/UxKgRAAdvQE0sTh7eCEwT-SSHr1ULMcspj1yPw2dBQkxDV-Y_fOHodKNyHbS2JwBEVLnVKF2X_TPOhwZG9m0hQ', 22 | 'https://res.wx.qq.com/op_res/Zmvv0fisUjaMjuqWLhWWkuzGktaXJEQt46EaKsCKeT06Z4tROseXN0joI7h2qwzqyx2FUy57cveZL-8iArI8_Q', 23 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJitcJIfp9P4VSWxi3126XeiyZ2BnnH0xg-oIXAUgHBgaHjBMwxzSjSkEkTMRqzlKZw', 24 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJnw5zq4f_XW3swKAowexqAbziuojU5W9v4CJixA-NDJShkfS0ne3KWY_6SB56yqb3g', 25 | ] 26 | return categories.map((name, i) => { 27 | return {name, image: images[i]} 28 | }) 29 | } 30 | 31 | export function getProducts() { 32 | let products = [ 33 | '小程序性能优化课程', 34 | '小程序直播企业实践案例', 35 | '微信客服轻松配置,入门必修', 36 | '小程序如何帮助传统医院数字化?', 37 | '帮你快速掌握小商店经营秘诀', 38 | '了解小程序开发动态,听官方为你解读新能力', 39 | '快速了解微信小程序在医疗行业的应用', 40 | '解析常见小程序违规类型', 41 | '想做互联网的生意,可以通过微信怎么经营呢?', 42 | '政务行业小程序实践' 43 | ] 44 | let images = [ 45 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU_LhYxhaP5JTy7TWgezsDY7RW_l_e04fR7oG7sCKmS8hc8mVeZaY6eUWT3nk-ww_ZQ', 46 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU-O3axOjUJGFgutF9Xc1JL1uxXFWYdW85mWG0Zvm5nv7rvP18CJ0q6-RRFM0xWLLog', 47 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU3ywQmrV-rSREDwo0Hp9m7iIZZ7Njvjq_TlOg_0ss0cgQL0pfKOuB2NRpAcwfALxvw', 48 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU1GROxmiPIBOCoA5Es44GxjN0KuCQQsoxEH33l05TCgk04n0dssHAIPxIV2ycSlSJA', 49 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU68wmBQzYcfQfuIAh1IKWq7OyG0EWxdWGhotYHFh-k-JpmkJ1Otq-mYUT8Dp3iucvg', 50 | 'https://res.wx.qq.com/op_res/UxKgRAAdvQE0sTh7eCEwT1ASo_fbE3TppPoza_9I7U7OreGNv8F8ltq80gqQSzxa-86bt-gWalLtgPDAhflj-w', 51 | 'https://res.wx.qq.com/op_res/UxKgRAAdvQE0sTh7eCEwT-SSHr1ULMcspj1yPw2dBQkxDV-Y_fOHodKNyHbS2JwBEVLnVKF2X_TPOhwZG9m0hQ', 52 | 'https://res.wx.qq.com/op_res/Zmvv0fisUjaMjuqWLhWWkuzGktaXJEQt46EaKsCKeT06Z4tROseXN0joI7h2qwzqyx2FUy57cveZL-8iArI8_Q', 53 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJitcJIfp9P4VSWxi3126XeiyZ2BnnH0xg-oIXAUgHBgaHjBMwxzSjSkEkTMRqzlKZw', 54 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJnw5zq4f_XW3swKAowexqAbziuojU5W9v4CJixA-NDJShkfS0ne3KWY_6SB56yqb3g', 55 | ] 56 | products = products.concat(products).map((name, id) => ({ 57 | id, 58 | name, 59 | image: images[(id % products.length)], 60 | comment: '一如既往的好', 61 | sales: 6500, 62 | discount: 0.01, 63 | price: 0.01 64 | })) 65 | return products 66 | } 67 | -------------------------------------------------------------------------------- /examples/card_transition/app.js: -------------------------------------------------------------------------------- 1 | App({}) 2 | -------------------------------------------------------------------------------- /examples/card_transition/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/list/list", 4 | "pages/detail/detail" 5 | ], 6 | "window": { 7 | "backgroundTextStyle": "light", 8 | "navigationBarBackgroundColor": "#fff", 9 | "navigationBarTitleText": "Weixin", 10 | "navigationBarTextStyle": "black" 11 | }, 12 | "style": "v2", 13 | "sitemapLocation": "sitemap.json", 14 | "renderer": "skyline", 15 | "rendererOptions": { 16 | "skyline": { 17 | "defaultDisplayBlock": true, 18 | "disableABTest": true, 19 | "sdkVersionBegin": "3.0.0", 20 | "sdkVersionEnd": "15.255.255" 21 | } 22 | }, 23 | "componentFramework": "glass-easel", 24 | "lazyCodeLoading": "requiredComponents" 25 | } -------------------------------------------------------------------------------- /examples/card_transition/app.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/awesome-skyline/25e2d93c2daf3f78d0908ff53ea305147d0c8978/examples/card_transition/app.wxss -------------------------------------------------------------------------------- /examples/card_transition/components/card/card.js: -------------------------------------------------------------------------------- 1 | const { shared } = wx.worklet 2 | 3 | const FlightDirection = { 4 | PUSH: 0, 5 | POP: 1, 6 | } 7 | 8 | Component({ 9 | options: { 10 | virtualHost: true, 11 | }, 12 | properties: { 13 | index: { 14 | type: Number, 15 | value: -1, 16 | }, 17 | item: { 18 | type: Object, 19 | value: {}, 20 | }, 21 | cardWidth: { 22 | type: Number, 23 | value: 0 24 | }, 25 | }, 26 | lifetimes: { 27 | created() { 28 | this.scale = shared(1) 29 | this.opacity = shared(0) 30 | this.direction = shared(0) 31 | this.srcWidth = shared('100%') 32 | this.radius = shared(5) 33 | 34 | const beginRect = shared(undefined) 35 | const endRect = shared(undefined) 36 | wx.worklet.runOnUI(() => { 37 | 'worklet' 38 | globalThis['RouteCardSrcRect'] = beginRect 39 | globalThis['RouteCardDestRect'] = endRect 40 | })() 41 | }, 42 | attached() { 43 | this.applyAnimatedStyle( 44 | '.card_wrap', 45 | () => { 46 | 'worklet' 47 | return { 48 | width: this.srcWidth.value, 49 | transform: `scale(${this.scale.value})`, 50 | } 51 | }, 52 | { 53 | immediate: false, 54 | flush: 'sync' 55 | }, 56 | () => {}, 57 | ) 58 | 59 | this.applyAnimatedStyle( 60 | '.card_img', 61 | () => { 62 | 'worklet' 63 | return { 64 | opacity: this.opacity.value, 65 | borderTopRightRadius: this.radius.value, // 不带单位默认是 px 66 | borderTopLeftRadius: this.radius.value, 67 | } 68 | }, 69 | { 70 | immediate: false, 71 | flush: 'sync' 72 | }, 73 | () => {}, 74 | ) 75 | 76 | this.applyAnimatedStyle( 77 | '.card_desc', 78 | () => { 79 | 'worklet' 80 | return { 81 | opacity: this.opacity.value, 82 | } 83 | }, 84 | { 85 | immediate: false, 86 | flush: 'sync' 87 | }, 88 | () => {}, 89 | ) 90 | }, 91 | }, 92 | 93 | methods: { 94 | navigateTo(e) { 95 | const { index, url, content, ratio, nickname } = e.currentTarget.dataset 96 | const urlContent = `../../pages/detail/detail?index=${index}&url=${encodeURIComponent(url)}&content=${content}&ratio=${ratio}&nickname=${nickname}` 97 | wx.navigateTo({ 98 | url: urlContent, 99 | routeType: 'CardScaleTransition', 100 | }) 101 | }, 102 | handleFrame(data) { 103 | 'worklet' 104 | this.direction.value = data.direction 105 | if (data.direction === FlightDirection.PUSH) { // 进入 106 | // 飞跃过程中,卡片从 100% 改为固定宽度,通过 scale 手动控制缩放 107 | this.srcWidth.value = `${data.begin.width}px` 108 | this.scale.value = data.current.width / data.begin.width 109 | this.opacity.value = 1 - data.progress 110 | this.radius.value = 0 111 | // this.shareImgHeight.value = data.begin.height 112 | 113 | } else if (data.direction === FlightDirection.POP) { // 返回 114 | this.scale.value = data.current.width / data.end.width 115 | this.opacity.value = data.progress 116 | this.radius.value = 5 117 | } 118 | 119 | // globalThis 是 UI 线程的全局变量,将 share-element 初始和目标尺寸保存起来,用于下一页面的缩放动画的计算 120 | // TODO: 后续计划优化这里的接口设计 121 | if (globalThis['RouteCardSrcRect'] && globalThis['RouteCardSrcRect'].value == undefined) { 122 | globalThis['RouteCardSrcRect'].value = data.begin 123 | } 124 | if (globalThis['RouteCardDestRect'] && globalThis['RouteCardDestRect'].value == undefined) { 125 | globalThis['RouteCardDestRect'].value = data.end 126 | } 127 | }, 128 | }, 129 | }) 130 | -------------------------------------------------------------------------------- /examples/card_transition/components/card/card.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {}, 4 | "componentFramework": "glass-easel" 5 | } -------------------------------------------------------------------------------- /examples/card_transition/components/card/card.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{item.content}} 9 | 10 | 11 | {{item.nickname}} 12 | {{item.like}} 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/card_transition/components/card/card.wxss: -------------------------------------------------------------------------------- 1 | .card { 2 | border-radius: 5px; 3 | overflow: hidden; 4 | flex-shrink: 0; 5 | width: 100%; 6 | background-color: white; 7 | } 8 | 9 | .card_wrap { 10 | position: absolute; 11 | transform-origin: 0 0; 12 | width: 100%; 13 | display: flex; 14 | flex-direction: column; 15 | } 16 | 17 | .card_img { 18 | width: 100%; 19 | border-top-right-radius: 5px; 20 | border-top-left-radius: 5px; 21 | } 22 | 23 | .card_content { 24 | padding: 8px; 25 | font-size: 14px; 26 | font-weight: 500; 27 | /* height: 54px; */ 28 | overflow: hidden; 29 | box-sizing: border-box; 30 | } 31 | 32 | .card_footer { 33 | padding: 0 8px 8px; 34 | display: flex; 35 | flex-direction: row; 36 | font-size: 12px; 37 | color: #666666; 38 | line-height: 20px; 39 | } 40 | 41 | .card_avatar { 42 | width: 20px; 43 | height: 20px; 44 | border-radius: 50%; 45 | } 46 | 47 | .card_nickname { 48 | flex: 1; 49 | padding-left: 4px; 50 | } 51 | -------------------------------------------------------------------------------- /examples/card_transition/components/navigation-bar/index.js: -------------------------------------------------------------------------------- 1 | Component({ 2 | options: { 3 | multipleSlots: true // 在组件定义时的选项中启用多slot支持 4 | }, 5 | /** 6 | * 组件的属性列表 7 | */ 8 | properties: { 9 | extClass: { 10 | type: String, 11 | value: '' 12 | }, 13 | title: { 14 | type: String, 15 | value: '' 16 | }, 17 | background: { 18 | type: String, 19 | value: '' 20 | }, 21 | color: { 22 | type: String, 23 | value: '' 24 | }, 25 | back: { 26 | type: Boolean, 27 | value: true 28 | }, 29 | loading: { 30 | type: Boolean, 31 | value: false 32 | }, 33 | animated: { 34 | // 显示隐藏的时候opacity动画效果 35 | type: Boolean, 36 | value: true 37 | }, 38 | show: { 39 | // 显示隐藏导航,隐藏的时候navigation-bar的高度占位还在 40 | type: Boolean, 41 | value: true, 42 | observer: '_showChange' 43 | }, 44 | // back为true的时候,返回的页面深度 45 | delta: { 46 | type: Number, 47 | value: 1 48 | } 49 | }, 50 | /** 51 | * 组件的初始数据 52 | */ 53 | data: { 54 | displayStyle: '' 55 | }, 56 | attached() { 57 | const rect = wx.getMenuButtonBoundingClientRect() 58 | wx.getSystemInfo({ 59 | success: (res) => { 60 | this.setData({ 61 | statusBarHeight: res.statusBarHeight, 62 | innerPaddingRight: `padding-right:${res.windowWidth - rect.left}px`, 63 | leftWidth: `width:${res.windowWidth - rect.left}px`, 64 | navBarHeight: rect.bottom + rect.top - res.statusBarHeight, 65 | }) 66 | } 67 | }) 68 | }, 69 | /** 70 | * 组件的方法列表 71 | */ 72 | methods: { 73 | _showChange(show) { 74 | const animated = this.data.animated 75 | let displayStyle = '' 76 | if (animated) { 77 | displayStyle = `opacity: ${show ? '1' : '0'};transition: opacity 0.5s;` 78 | } else { 79 | displayStyle = `display: ${show ? '' : 'none'}` 80 | } 81 | this.setData({ 82 | displayStyle 83 | }) 84 | }, 85 | back() { 86 | const data = this.data 87 | if (data.delta) { 88 | wx.navigateBack({ 89 | delta: data.delta 90 | }) 91 | } 92 | this.triggerEvent('back', { delta: data.delta }, {}) 93 | } 94 | } 95 | }) 96 | -------------------------------------------------------------------------------- /examples/card_transition/components/navigation-bar/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {}, 4 | "addGlobalClass": true, 5 | "componentFramework": "glass-easel" 6 | } -------------------------------------------------------------------------------- /examples/card_transition/components/navigation-bar/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {{title}} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/card_transition/components/navigation-bar/index.wxss: -------------------------------------------------------------------------------- 1 | .weui-navigation-bar { 2 | overflow: hidden; 3 | color: rgba(0, 0, 0, .9); 4 | width: 100vw; 5 | } 6 | 7 | .weui-navigation-bar__placeholder { 8 | background: #f7f7f7; 9 | position: relative; 10 | } 11 | 12 | .weui-navigation-bar__inner, .weui-navigation-bar__inner .weui-navigation-bar__left { 13 | display: flex; 14 | align-items: center; 15 | flex-direction: row; 16 | } 17 | 18 | .weui-navigation-bar__inner { 19 | position: relative; 20 | padding-right: 95px; 21 | width: 100vw; 22 | box-sizing: border-box; 23 | } 24 | 25 | .weui-navigation-bar__inner .weui-navigation-bar__left { 26 | position: relative; 27 | width: 95px; 28 | padding-left: 16px; 29 | box-sizing: border-box; 30 | } 31 | 32 | .weui-navigation-bar__btn_goback_wrapper { 33 | padding: 11px 18px 11px 16px; 34 | margin: -11px -18px -11px -16px; 35 | } 36 | 37 | .weui-navigation-bar__inner .weui-navigation-bar__left .weui-navigation-bar__btn_goback { 38 | font-size: 12px; 39 | width: 12px; 40 | height: 24px; 41 | background: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z'/%3E%3C/svg%3E") no-repeat 50% 50%; 42 | background-size: cover; 43 | } 44 | 45 | .weui-navigation-bar__inner .weui-navigation-bar__center { 46 | font-size: 17px; 47 | text-align: center; 48 | position: relative; 49 | flex: 1; 50 | display: flex; 51 | flex-direction: column; 52 | align-items: center; 53 | justify-content: center; 54 | font-weight: bold; 55 | } 56 | 57 | @media(prefers-color-scheme: dark) { 58 | .weui-navigation-bar { 59 | color: hsla(0, 0%, 100%, .8); 60 | } 61 | .weui-navigation-bar__inner { 62 | background-color: #1f1f1f; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/card_transition/pages/detail/detail.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {}, 3 | "backgroundColorContent": "#00000000", 4 | "disableScroll": true, 5 | "navigationStyle": "custom" 6 | } -------------------------------------------------------------------------------- /examples/card_transition/pages/detail/detail.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{nickname}} 8 | 关注 9 | 10 | 11 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {{content}} 41 | 42 | 🔥 Skyline 以性能为首要目标,提供更为接近原生的用户体验,Skyline 具有以下特点: 43 | 44 | 🌟 界面更不容易被逻辑阻塞,进一步减少卡顿 45 | 🌟 无需为每个页面新建一个 JS 引擎实例(WebView),减少了内存、时间开销 46 | 🌟 框架可以在页面之间共享更多的资源,进一步减少运行时内存、时间开销 47 | 🌟 框架的代码之间无需再通过 JSBridge 进行数据交换,减少了大量通信时间开销 48 | 49 | 👇👇👇 支持了一些 Web 所缺失的但很重要的能力,以满足开发者实现更好的交互体验: 50 | 51 | 🔸 Worklet 动画:能够在渲染线程同步运行动画相关逻辑。 52 | 🔸 手势系统:在渲染线程同步监听手势、执行手势相关逻辑;支持手势协商处理; 53 | 🔸 自定义路由:实现自定义路由动画和交互。 54 | 🔸 共享元素动画:将上一个页面的元素“共享”到下一个页面,并伴随着过渡动画。 55 | 56 | 57 | 58 | 59 | 60 | 61 | ❤️ 62 | 317 63 | 64 | 65 | ⭐️ 66 | 723 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /examples/card_transition/pages/detail/detail.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100vh; 5 | background-color: transparent; 6 | } 7 | 8 | view, scroll-view, page, image { 9 | box-sizing: border-box; 10 | } 11 | 12 | #fake-host { 13 | background-color: #FFF; 14 | border-radius: 10px; 15 | overflow: hidden; 16 | } 17 | 18 | #page { 19 | display: flex; 20 | flex-direction: column; 21 | height: 100vh; 22 | } 23 | 24 | .navigation-bar { 25 | padding-top: 44px; 26 | padding-top: env(safe-area-inset-top); 27 | background-color: white; 28 | } 29 | 30 | .navigation-bar-content { 31 | height: 44px; 32 | display: flex; 33 | flex-direction: row; 34 | padding: 0 20px; 35 | justify-content: center; 36 | align-items: center; 37 | font-size: 14px; 38 | } 39 | 40 | .navigation-bar-avatar { 41 | width: 24px; 42 | height: 24px; 43 | border-radius: 100%; 44 | } 45 | 46 | .navigation-bar-title { 47 | flex: 1; 48 | padding-left: 8px; 49 | } 50 | 51 | .navigation-bar-follow { 52 | color: red; 53 | border: .5px solid red; 54 | border-radius: 15px; 55 | padding: 3px 15px; 56 | margin-right: 80px; 57 | } 58 | 59 | .detail-image { 60 | width: 100%; 61 | height: 100%; 62 | } 63 | 64 | .detail-content { 65 | padding: 15px; 66 | } 67 | 68 | .detail-title { 69 | font-weight: bold; 70 | font-size: 18px; 71 | padding-bottom: 3px; 72 | } 73 | 74 | .detail-p { 75 | padding-bottom: 2px; 76 | } 77 | 78 | .footer { 79 | padding-bottom: env(safe-area-inset-bottom); 80 | font-size: 14px; 81 | } 82 | 83 | .footer-content { 84 | display: flex; 85 | align-items: center; 86 | flex-direction: row; 87 | height: 50px; 88 | } 89 | 90 | .footer .footer-input { 91 | flex: 1; 92 | background-color: #EEE; 93 | margin-left: 15px; 94 | margin-right: 15px; 95 | padding-left: 15px; 96 | height: 40px; 97 | border-radius: 20px; 98 | } 99 | 100 | .footer span { 101 | padding-right: 15px; 102 | } 103 | 104 | .footer-icon { 105 | font-size: 26px; 106 | } 107 | -------------------------------------------------------------------------------- /examples/card_transition/pages/list/list.js: -------------------------------------------------------------------------------- 1 | import { installRouteBuilder } from './route' 2 | import { generateGridList, compareVersion } from './utils' 3 | 4 | const { screenWidth } = wx.getSystemInfoSync() 5 | 6 | Component({ 7 | data: { 8 | padding: 4, 9 | gridList: generateGridList(100, 2), 10 | cardWidth: (screenWidth - 4 * 2 - 4) / 2, // 减去间距 11 | }, 12 | 13 | lifetimes: { 14 | created() { 15 | const {SDKVersion} = wx.getSystemInfoSync() 16 | if (compareVersion(SDKVersion, '2.30.1') < 0) { 17 | wx.showModal({ 18 | content: '基础库版本低于 v2.30.1 可能会有显示问题,建议升级微信体验。', 19 | showCancel: false 20 | }) 21 | } 22 | installRouteBuilder() 23 | }, 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /examples/card_transition/pages/list/list.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "navigation-bar": "../../components/navigation-bar/index", 4 | "card": "../../components/card/card" 5 | }, 6 | "disableScroll": true, 7 | "navigationStyle": "custom" 8 | } -------------------------------------------------------------------------------- /examples/card_transition/pages/list/list.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 15 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/card_transition/pages/list/list.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100vh; 5 | } 6 | 7 | scroll-view { 8 | background-color: #f4f4f4; 9 | } 10 | 11 | view, scroll-view, page, image { 12 | box-sizing: border-box; 13 | } 14 | -------------------------------------------------------------------------------- /examples/card_transition/pages/list/route.js: -------------------------------------------------------------------------------- 1 | const AnimationStatus = { 2 | dismissed: 0, // The animation is stopped at the beginning. 3 | forward: 1, // The animation is running from beginning to end. 4 | reverse: 2, // The animation is running backwards, from end to beginning. 5 | completed: 3, // The animation is stopped at the end. 6 | } 7 | 8 | const { Easing, shared } = wx.worklet 9 | 10 | export const Curves = { 11 | linearToEaseOut: Easing.cubicBezier(0.35, 0.91, 0.33, 0.97), 12 | easeInToLinear: Easing.cubicBezier(0.67, 0.03, 0.65, 0.09), 13 | fastOutSlowIn: Easing.cubicBezier(0.4, 0.0, 0.2, 1.0), 14 | slowOutFastIn: Easing.cubicBezier(0.0, 0.8, 1.0, 0.6), 15 | easeOutCubic: Easing.cubicBezier(0.215, 0.61, 0.355, 1.0), 16 | } 17 | 18 | export function CurveAnimation({ 19 | animation, animationStatus, curve, reverseCurve 20 | }) { 21 | const { derived } = wx.worklet 22 | 23 | return derived(() => { 24 | 'worklet' 25 | 26 | const useForwardCurve = !reverseCurve || animationStatus.value !== AnimationStatus.reverse 27 | const activeCurve = useForwardCurve ? curve : reverseCurve 28 | 29 | const t = animation.value 30 | if (!activeCurve) return t 31 | if (t === 0 || t === 1) return t 32 | return activeCurve(t) 33 | }) 34 | } 35 | 36 | export const lerp = (begin, end, t) => { 37 | 'worklet' 38 | return begin + (end - begin) * t 39 | } 40 | 41 | const ScaleTransitionRouteBuilder = (routeContext) => { 42 | const { 43 | primaryAnimation, 44 | primaryAnimationStatus, 45 | userGestureInProgress, 46 | } = routeContext 47 | 48 | const shareEleTop = shared(0) 49 | routeContext.shareEleTop = shareEleTop 50 | 51 | const _curvePrimaryAnimation = CurveAnimation({ 52 | animation: primaryAnimation, 53 | animationStatus: primaryAnimationStatus, 54 | curve: Easing.in(Curves.fastOutSlowIn), 55 | reverseCurve: Easing.out(Curves.fastOutSlowIn) 56 | }) 57 | 58 | // 每次路由动画结束(进入或返回)都会重置一下 59 | const reset = () => { 60 | 'worklet' 61 | if (globalThis['RouteCardSrcRect']) { 62 | globalThis['RouteCardSrcRect'].value = undefined 63 | } 64 | if (globalThis['RouteCardDestRect']) { 65 | globalThis['RouteCardDestRect'].value = undefined 66 | } 67 | } 68 | 69 | const handlePrimaryAnimation = () => { 70 | 'worklet' 71 | const status = primaryAnimationStatus.value 72 | // 手势返回时,动画在详情页处理,此处顶层节点只做整体透明度淡出 73 | if (userGestureInProgress.value) { 74 | return { 75 | opacity: Easing.out(Easing.cubicBezier(0.5, 0, 0.7, 0.5)(primaryAnimation.value)), 76 | } 77 | } 78 | 79 | if (status == AnimationStatus.dismissed) { 80 | reset() 81 | return { 82 | transform: `translate(0, 0) scale(0)`, 83 | } 84 | } 85 | 86 | if (status == AnimationStatus.completed ) { 87 | reset() 88 | return { 89 | transform: `translate(0, 0) scale(1)`, 90 | } 91 | } 92 | 93 | let transX = 0 94 | let transY = 0 95 | let scale = status === AnimationStatus.reverse ? 1 : 0 96 | 97 | // 进入或者接口返回 98 | if (globalThis['RouteCardSrcRect'] && globalThis['RouteCardSrcRect'].value != undefined) { 99 | const begin = globalThis['RouteCardSrcRect'].value 100 | const end = globalThis['RouteCardDestRect'].value 101 | 102 | if (status === AnimationStatus.forward) { 103 | shareEleTop.value = end.top 104 | } 105 | 106 | let t = _curvePrimaryAnimation.value 107 | if (status === AnimationStatus.reverse || status === AnimationStatus.dismissed) { 108 | t = 1 - t 109 | } 110 | 111 | const shareEleX = lerp(begin.left, end.left, t) 112 | const shareEleY = lerp(begin.top, end.top, t) 113 | const shareEleW = lerp(begin.width, end.width, t) 114 | 115 | transX = shareEleX 116 | if (status === AnimationStatus.reverse) { 117 | scale = shareEleW / begin.width 118 | transY = shareEleY - begin.top * scale 119 | } else { 120 | scale = shareEleW / end.width 121 | transY = shareEleY - end.top * scale 122 | } 123 | } 124 | 125 | return { 126 | transform: `translate(${transX}px, ${transY}px) scale(${scale})`, 127 | transformOrigin: '0 0', 128 | opacity: _curvePrimaryAnimation.value, 129 | } 130 | } 131 | 132 | return { 133 | opaque: false, 134 | handlePrimaryAnimation, 135 | transitionDuration: 250, 136 | reverseTransitionDuration: 250, 137 | canTransitionTo: false, 138 | canTransitionFrom: false, 139 | barrierColor: "rgba(0, 0, 0, 0.3)", 140 | } 141 | } 142 | 143 | let hasInstalled = false 144 | export function installRouteBuilder() { 145 | if (hasInstalled) { 146 | return 147 | } 148 | wx.router.addRouteBuilder('CardScaleTransition', ScaleTransitionRouteBuilder) 149 | hasInstalled = true 150 | } 151 | -------------------------------------------------------------------------------- /examples/card_transition/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appid": "wxe5f52902cf4de896", 3 | "compileType": "miniprogram", 4 | "libVersion": "latest", 5 | "packOptions": { 6 | "ignore": [], 7 | "include": [] 8 | }, 9 | "setting": { 10 | "coverView": true, 11 | "es6": true, 12 | "postcss": true, 13 | "minified": true, 14 | "enhance": true, 15 | "showShadowRootInWxmlPanel": true, 16 | "packNpmRelationList": [], 17 | "babelSetting": { 18 | "ignore": [], 19 | "disablePlugins": [], 20 | "outputPath": "" 21 | }, 22 | "condition": false, 23 | "skylineRenderEnable": false, 24 | "compileWorklet": true 25 | }, 26 | "condition": {}, 27 | "editorSetting": { 28 | "tabIndent": "insertSpaces", 29 | "tabSize": 2 30 | } 31 | } -------------------------------------------------------------------------------- /examples/card_transition/project.private.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", 3 | "projectname": "card_transition", 4 | "setting": { 5 | "compileHotReLoad": false, 6 | "skylineRenderEnable": false 7 | }, 8 | "condition": { 9 | "miniprogram": { 10 | "list": [ 11 | { 12 | "name": "", 13 | "pathName": "detail/detail", 14 | "query": "index=1&url=https%3A%2F%2Fpicsum.photos%2F300%2F400%3Frandom%3D1&content=Hayya Hayya!我来小红书啦&ratio=0.75", 15 | "launchMode": "default", 16 | "scene": null 17 | }, 18 | { 19 | "name": "", 20 | "pathName": "list/list", 21 | "query": "", 22 | "launchMode": "default", 23 | "scene": null 24 | } 25 | ] 26 | } 27 | }, 28 | "libVersion": "latest" 29 | } -------------------------------------------------------------------------------- /examples/card_transition/sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /examples/expanded-scroll-view/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Eslint config file 3 | * Documentation: https://eslint.org/docs/user-guide/configuring/ 4 | * Install the Eslint extension before using this feature. 5 | */ 6 | module.exports = { 7 | env: { 8 | es6: true, 9 | browser: true, 10 | node: true, 11 | }, 12 | ecmaFeatures: { 13 | modules: true, 14 | }, 15 | parserOptions: { 16 | ecmaVersion: 2018, 17 | sourceType: 'module', 18 | }, 19 | globals: { 20 | wx: true, 21 | App: true, 22 | Page: true, 23 | getCurrentPages: true, 24 | getApp: true, 25 | Component: true, 26 | requirePlugin: true, 27 | requireMiniProgram: true, 28 | }, 29 | // extends: 'eslint:recommended', 30 | rules: {}, 31 | } 32 | -------------------------------------------------------------------------------- /examples/expanded-scroll-view/app.js: -------------------------------------------------------------------------------- 1 | // app.js 2 | App({}) 3 | -------------------------------------------------------------------------------- /examples/expanded-scroll-view/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index" 4 | ], 5 | "window": { 6 | "backgroundTextStyle": "light", 7 | "navigationBarBackgroundColor": "#fff", 8 | "navigationBarTitleText": "Weixin", 9 | "navigationBarTextStyle": "black" 10 | }, 11 | "style": "v2", 12 | "renderer": "skyline", 13 | "rendererOptions": { 14 | "skyline": { 15 | "defaultDisplayBlock": true, 16 | "disableABTest": true 17 | } 18 | }, 19 | "lazyCodeLoading": "requiredComponents", 20 | "sitemapLocation": "sitemap.json" 21 | } -------------------------------------------------------------------------------- /examples/expanded-scroll-view/app.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/awesome-skyline/25e2d93c2daf3f78d0908ff53ea305147d0c8978/examples/expanded-scroll-view/app.wxss -------------------------------------------------------------------------------- /examples/expanded-scroll-view/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {}, 3 | "disableScroll": true, 4 | "navigationStyle": "custom", 5 | "renderer": "skyline", 6 | "componentFramework": "glass-easel" 7 | } -------------------------------------------------------------------------------- /examples/expanded-scroll-view/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileType": "miniprogram", 3 | "setting": { 4 | "coverView": true, 5 | "es6": true, 6 | "postcss": true, 7 | "minified": true, 8 | "enhance": true, 9 | "showShadowRootInWxmlPanel": true, 10 | "packNpmRelationList": [], 11 | "babelSetting": { 12 | "ignore": [], 13 | "disablePlugins": [], 14 | "outputPath": "" 15 | }, 16 | "condition": false, 17 | "ignoreUploadUnusedFiles": true, 18 | "compileWorklet": true 19 | }, 20 | "condition": {}, 21 | "editorSetting": { 22 | "tabIndent": "insertSpaces", 23 | "tabSize": 2 24 | }, 25 | "packOptions": { 26 | "ignore": [], 27 | "include": [] 28 | }, 29 | "appid": "wxe5f52902cf4de896" 30 | } -------------------------------------------------------------------------------- /examples/expanded-scroll-view/project.private.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", 3 | "projectname": "expanded-scroll-view", 4 | "setting": { 5 | "compileHotReLoad": false, 6 | "skylineRenderEnable": true 7 | }, 8 | "libVersion": "latest" 9 | } -------------------------------------------------------------------------------- /examples/expanded-scroll-view/sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /examples/expanded-scroll-view/util.js: -------------------------------------------------------------------------------- 1 | export function getCategory() { 2 | let categorys = [ 3 | '中小商户', 4 | '商超零售', 5 | '品牌服饰', 6 | '餐饮', 7 | '医疗', 8 | '酒旅', 9 | '政务', 10 | '开发技术', 11 | '产品能力', 12 | '运营规范', 13 | ] 14 | return categorys 15 | } 16 | 17 | export function getProducts() { 18 | let products = [ 19 | '小程序性能优化课程', 20 | '小程序直播企业实践案例', 21 | '微信客服轻松配置,入门必修', 22 | '小程序如何帮助传统医院数字化?', 23 | '帮你快速掌握小商店经营秘诀', 24 | '了解小程序开发动态,听官方为你解读新能力', 25 | '快速了解微信小程序在医疗行业的应用', 26 | '解析常见小程序违规类型', 27 | '想做互联网的生意,可以通过微信怎么经营呢?', 28 | '政务行业小程序实践' 29 | ] 30 | let images = [ 31 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU_LhYxhaP5JTy7TWgezsDY7RW_l_e04fR7oG7sCKmS8hc8mVeZaY6eUWT3nk-ww_ZQ', 32 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU-O3axOjUJGFgutF9Xc1JL1uxXFWYdW85mWG0Zvm5nv7rvP18CJ0q6-RRFM0xWLLog', 33 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU3ywQmrV-rSREDwo0Hp9m7iIZZ7Njvjq_TlOg_0ss0cgQL0pfKOuB2NRpAcwfALxvw', 34 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU1GROxmiPIBOCoA5Es44GxjN0KuCQQsoxEH33l05TCgk04n0dssHAIPxIV2ycSlSJA', 35 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU68wmBQzYcfQfuIAh1IKWq7OyG0EWxdWGhotYHFh-k-JpmkJ1Otq-mYUT8Dp3iucvg', 36 | 'https://res.wx.qq.com/op_res/UxKgRAAdvQE0sTh7eCEwT1ASo_fbE3TppPoza_9I7U7OreGNv8F8ltq80gqQSzxa-86bt-gWalLtgPDAhflj-w', 37 | 'https://res.wx.qq.com/op_res/UxKgRAAdvQE0sTh7eCEwT-SSHr1ULMcspj1yPw2dBQkxDV-Y_fOHodKNyHbS2JwBEVLnVKF2X_TPOhwZG9m0hQ', 38 | 'https://res.wx.qq.com/op_res/Zmvv0fisUjaMjuqWLhWWkuzGktaXJEQt46EaKsCKeT06Z4tROseXN0joI7h2qwzqyx2FUy57cveZL-8iArI8_Q', 39 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJitcJIfp9P4VSWxi3126XeiyZ2BnnH0xg-oIXAUgHBgaHjBMwxzSjSkEkTMRqzlKZw', 40 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJnw5zq4f_XW3swKAowexqAbziuojU5W9v4CJixA-NDJShkfS0ne3KWY_6SB56yqb3g', 41 | ] 42 | products = products.map((name, id) => ({ 43 | name, 44 | image: images[(id % products.length)], 45 | comment: '一如既往的好', 46 | sales: 6500, 47 | discount: 0.01, 48 | price: 0.01 49 | })) 50 | return products 51 | } -------------------------------------------------------------------------------- /examples/half-screen/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Eslint config file 3 | * Documentation: https://eslint.org/docs/user-guide/configuring/ 4 | * Install the Eslint extension before using this feature. 5 | */ 6 | module.exports = { 7 | env: { 8 | es6: true, 9 | browser: true, 10 | node: true, 11 | }, 12 | ecmaFeatures: { 13 | modules: true, 14 | }, 15 | parserOptions: { 16 | ecmaVersion: 2018, 17 | sourceType: 'module', 18 | }, 19 | globals: { 20 | wx: true, 21 | App: true, 22 | Page: true, 23 | getCurrentPages: true, 24 | getApp: true, 25 | Component: true, 26 | requirePlugin: true, 27 | requireMiniProgram: true, 28 | }, 29 | // extends: 'eslint:recommended', 30 | rules: {}, 31 | } 32 | -------------------------------------------------------------------------------- /examples/half-screen/app.js: -------------------------------------------------------------------------------- 1 | // app.js 2 | App({}) 3 | -------------------------------------------------------------------------------- /examples/half-screen/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index" 4 | ], 5 | "window": { 6 | "backgroundTextStyle": "light", 7 | "navigationBarBackgroundColor": "#fff", 8 | "navigationBarTitleText": "Weixin", 9 | "navigationBarTextStyle": "black" 10 | }, 11 | "style": "v2", 12 | "renderer": "skyline", 13 | "rendererOptions": { 14 | "skyline": { 15 | "defaultDisplayBlock": true, 16 | "disableABTest": true, 17 | "sdkVersionBegin": "3.0.0", 18 | "sdkVersionEnd": "15.255.255" 19 | } 20 | }, 21 | "lazyCodeLoading": "requiredComponents", 22 | "componentFramework": "glass-easel", 23 | "sitemapLocation": "sitemap.json" 24 | } -------------------------------------------------------------------------------- /examples/half-screen/app.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/awesome-skyline/25e2d93c2daf3f78d0908ff53ea305147d0c8978/examples/half-screen/app.wxss -------------------------------------------------------------------------------- /examples/half-screen/pages/index/index.js: -------------------------------------------------------------------------------- 1 | import { getCommentList } from "./comment-data" 2 | 3 | const { shared, timing } = wx.worklet 4 | 5 | const GestureState = { 6 | POSSIBLE: 0, // 0 此时手势未识别,如 panDown等 7 | BEGIN: 1, // 1 手势已识别 8 | ACTIVE: 2, // 2 连续手势活跃状态 9 | END: 3, // 3 手势终止 10 | CANCELLED: 4, // 4 手势取消, 11 | } 12 | 13 | Component({ 14 | data: { 15 | list: getCommentList(), 16 | }, 17 | lifetimes: { 18 | created() { 19 | this.transY = shared(1000) 20 | this.scrollTop = shared(0) 21 | this.startPan = shared(true) 22 | this.commentHeight = shared(1000) 23 | }, 24 | ready() { 25 | const query = this.createSelectorQuery() 26 | // ready 生命周期里才能获取到首屏的布局信息 27 | query.select('.comment-container').boundingClientRect() 28 | query.exec((res) => { 29 | this.transY.value = this.commentHeight.value = res[0].height 30 | }) 31 | // 通过 transY 一个 SharedValue 控制半屏的位置 32 | this.applyAnimatedStyle('.comment-container', () => { 33 | 'worklet' 34 | return { transform: `translateY(${this.transY.value}px)` } 35 | }) 36 | }, 37 | }, 38 | methods: { 39 | onTapOpenComment() { 40 | this.openComment(300) 41 | }, 42 | openComment(duration) { 43 | 'worklet' 44 | this.transY.value = timing(0, { duration }) 45 | }, 46 | onTapCloseComment() { 47 | this.closeComment() 48 | }, 49 | closeComment() { 50 | 'worklet' 51 | this.transY.value = timing(this.commentHeight.value, { duration: 200 }) 52 | }, 53 | // shouldPanResponse 和 shouldScrollViewResponse 用于 pan 手势和 scroll-view 滚动手势的协商 54 | shouldPanResponse() { 55 | 'worklet' 56 | return this.startPan.value 57 | }, 58 | shouldScrollViewResponse(pointerEvent) { 59 | 'worklet' 60 | // transY > 0 说明 pan 手势在移动半屏,此时滚动不应生效 61 | if (this.transY.value > 0) return false 62 | const scrollTop = this.scrollTop.value 63 | const { deltaY } = pointerEvent 64 | // deltaY > 0 是往上滚动,scrollTop <= 0 是滚动到顶部边界,此时 pan 开始生效,滚动不生效 65 | const result = scrollTop <= 0 && deltaY > 0 66 | this.startPan.value = result 67 | return !result 68 | }, 69 | handlePan(gestureEvent) { 70 | 'worklet' 71 | if (gestureEvent.state === GestureState.ACTIVE) { 72 | const curPosition = this.transY.value 73 | const destination = Math.max(0, curPosition + gestureEvent.deltaY) 74 | if (curPosition === destination) return 75 | this.transY.value = destination 76 | } 77 | 78 | if (gestureEvent.state === GestureState.END || gestureEvent.state === GestureState.CANCELLED) { 79 | if (gestureEvent.velocityY > 500 && this.transY.value > 50) { 80 | this.closeComment() 81 | } else if (this.transY.value > this.commentHeight.value / 2) { 82 | this.closeComment() 83 | } else { 84 | this.openComment(100) 85 | } 86 | } 87 | }, 88 | adjustDecelerationVelocity(velocity) { 89 | 'worklet' 90 | const scrollTop = this.scrollTop.value 91 | return scrollTop <= 0 ? 0 : velocity 92 | }, 93 | handleScroll(evt) { 94 | 'worklet' 95 | this.scrollTop.value = evt.detail.scrollTop 96 | }, 97 | }, 98 | }) 99 | -------------------------------------------------------------------------------- /examples/half-screen/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {}, 3 | "disableScroll": true, 4 | "navigationStyle": "custom" 5 | } -------------------------------------------------------------------------------- /examples/half-screen/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 打开留言 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 留言 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {{item.userName}} 26 | {{item.comment}} 27 | 28 | 29 | 30 | 31 | 32 | {{subItem.userName}} 回复 {{subItem.replyUserName}} 33 | {{subItem.comment}} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/half-screen/pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | display: flex; 3 | flex-direction: column; 4 | padding-top: env(safe-area-inset-top); 5 | width: 100vw; 6 | height: 100vh; 7 | color: #1A191E; 8 | } 9 | page, view { 10 | box-sizing: border-box; 11 | } 12 | pan-gesture-handler, vertical-drag-gesture-handler { 13 | display: flex; 14 | flex-direction: column; 15 | overflow: hidden; 16 | } 17 | .container { 18 | flex: 1; 19 | width: 100vw; 20 | min-height: auto; 21 | overflow: hidden; 22 | } 23 | .container image { 24 | width: 100vw; 25 | } 26 | 27 | .open-comment { 28 | display: flex; 29 | flex-direction: column; 30 | flex-shrink: 0; 31 | width: 100%; 32 | background-color: white; 33 | } 34 | .open-comment-wording { 35 | height: 66px; 36 | display: flex; 37 | justify-content: center; 38 | align-items: center; 39 | } 40 | .safe-area-inset-bottom { 41 | height: env(safe-area-inset-bottom); 42 | } 43 | 44 | .comment-container { 45 | width: 100vw; 46 | height: 70vh; 47 | display: flex; 48 | flex-direction: column; 49 | position: absolute; 50 | bottom: 0; 51 | z-index: 999; 52 | background-color: white; 53 | border-top-left-radius: 20px; 54 | border-top-right-radius: 20px; 55 | box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2); 56 | transform: translateY(100%); 57 | } 58 | .comment-header { 59 | width: 100%; 60 | font-size: 16px; 61 | text-align: center; 62 | padding: 15px 0; 63 | } 64 | .close-comment { 65 | position: absolute; 66 | left: 20px; 67 | font-size: 10px; 68 | font-weight: bold; 69 | background-color: #F8F8F8; 70 | border-radius: 100%; 71 | width: 20px; 72 | height: 20px; 73 | line-height: 20px; 74 | z-index: 1; 75 | } 76 | .comment-list { 77 | flex: 1; 78 | overflow: hidden; 79 | } 80 | .comment-item { 81 | padding: 0 20px 20px; 82 | font-size: 13px; 83 | line-height: 1.4; 84 | } 85 | .main-comment, .sub-comment { 86 | display: flex; 87 | flex-direction: row; 88 | } 89 | .sub-comment { 90 | padding: 10px 22px 0; 91 | } 92 | 93 | .user-head-img { 94 | width: 33px; 95 | height: 33px; 96 | border-radius: 50%; 97 | margin-top: 5px; 98 | } 99 | .others { 100 | flex: 1; 101 | margin-left: 10px; 102 | } 103 | .user-name { 104 | white-space: nowrap; 105 | overflow: hidden; 106 | text-overflow: ellipsis; 107 | } 108 | .content { 109 | margin-top: 2px; 110 | } 111 | -------------------------------------------------------------------------------- /examples/half-screen/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appid": "wxe5f52902cf4de896", 3 | "compileType": "miniprogram", 4 | "libVersion": "latest", 5 | "packOptions": { 6 | "ignore": [], 7 | "include": [] 8 | }, 9 | "setting": { 10 | "coverView": true, 11 | "es6": true, 12 | "postcss": true, 13 | "minified": true, 14 | "enhance": true, 15 | "showShadowRootInWxmlPanel": true, 16 | "packNpmRelationList": [], 17 | "babelSetting": { 18 | "ignore": [], 19 | "disablePlugins": [], 20 | "outputPath": "" 21 | }, 22 | "condition": false, 23 | "skylineRenderEnable": true, 24 | "compileWorklet": true 25 | }, 26 | "condition": {}, 27 | "editorSetting": { 28 | "tabIndent": "insertSpaces", 29 | "tabSize": 2 30 | } 31 | } -------------------------------------------------------------------------------- /examples/half-screen/project.private.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", 3 | "projectname": "half-screen", 4 | "setting": { 5 | "compileHotReLoad": false, 6 | "skylineRenderEnable": true 7 | }, 8 | "libVersion": "latest" 9 | } -------------------------------------------------------------------------------- /examples/half-screen/sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /examples/product-list/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Eslint config file 3 | * Documentation: https://eslint.org/docs/user-guide/configuring/ 4 | * Install the Eslint extension before using this feature. 5 | */ 6 | module.exports = { 7 | env: { 8 | es6: true, 9 | browser: true, 10 | node: true, 11 | }, 12 | ecmaFeatures: { 13 | modules: true, 14 | }, 15 | parserOptions: { 16 | ecmaVersion: 2018, 17 | sourceType: 'module', 18 | }, 19 | globals: { 20 | wx: true, 21 | App: true, 22 | Page: true, 23 | getCurrentPages: true, 24 | getApp: true, 25 | Component: true, 26 | requirePlugin: true, 27 | requireMiniProgram: true, 28 | }, 29 | // extends: 'eslint:recommended', 30 | rules: {}, 31 | } 32 | -------------------------------------------------------------------------------- /examples/product-list/app.js: -------------------------------------------------------------------------------- 1 | // app.js 2 | App({}) 3 | -------------------------------------------------------------------------------- /examples/product-list/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index" 4 | ], 5 | "window": { 6 | "backgroundTextStyle": "light", 7 | "navigationBarBackgroundColor": "#fff", 8 | "navigationBarTitleText": "Weixin", 9 | "navigationBarTextStyle": "black" 10 | }, 11 | "style": "v2", 12 | "renderer": "skyline", 13 | "rendererOptions": { 14 | "skyline": { 15 | "defaultDisplayBlock": true, 16 | "disableABTest": true, 17 | "sdkVersionBegin": "3.0.0", 18 | "sdkVersionEnd": "15.255.255" 19 | } 20 | }, 21 | "lazyCodeLoading": "requiredComponents", 22 | "componentFramework": "glass-easel", 23 | "sitemapLocation": "sitemap.json" 24 | } -------------------------------------------------------------------------------- /examples/product-list/app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | view { 3 | box-sizing: border-box; 4 | } -------------------------------------------------------------------------------- /examples/product-list/components/navigation-bar/index.js: -------------------------------------------------------------------------------- 1 | Component({ 2 | options: { 3 | multipleSlots: true // 在组件定义时的选项中启用多slot支持 4 | }, 5 | /** 6 | * 组件的属性列表 7 | */ 8 | properties: { 9 | extClass: { 10 | type: String, 11 | value: '' 12 | }, 13 | title: { 14 | type: String, 15 | value: '' 16 | }, 17 | background: { 18 | type: String, 19 | value: '' 20 | }, 21 | color: { 22 | type: String, 23 | value: '' 24 | }, 25 | back: { 26 | type: Boolean, 27 | value: true 28 | }, 29 | loading: { 30 | type: Boolean, 31 | value: false 32 | }, 33 | animated: { 34 | // 显示隐藏的时候opacity动画效果 35 | type: Boolean, 36 | value: true 37 | }, 38 | show: { 39 | // 显示隐藏导航,隐藏的时候navigation-bar的高度占位还在 40 | type: Boolean, 41 | value: true, 42 | observer: '_showChange' 43 | }, 44 | // back为true的时候,返回的页面深度 45 | delta: { 46 | type: Number, 47 | value: 1 48 | } 49 | }, 50 | /** 51 | * 组件的初始数据 52 | */ 53 | data: { 54 | displayStyle: '' 55 | }, 56 | attached() { 57 | const rect = wx.getMenuButtonBoundingClientRect() 58 | wx.getSystemInfo({ 59 | success: (res) => { 60 | this.setData({ 61 | statusBarHeight: res.statusBarHeight, 62 | innerPaddingRight: `padding-right:${res.windowWidth - rect.left}px`, 63 | leftWidth: `width:${res.windowWidth - rect.left}px`, 64 | navBarHeight: rect.bottom + rect.top - res.statusBarHeight, 65 | }) 66 | } 67 | }) 68 | }, 69 | /** 70 | * 组件的方法列表 71 | */ 72 | methods: { 73 | _showChange(show) { 74 | const animated = this.data.animated 75 | let displayStyle = '' 76 | if (animated) { 77 | displayStyle = `opacity: ${show ? '1' : '0'};transition: opacity 0.5s;` 78 | } else { 79 | displayStyle = `display: ${show ? '' : 'none'}` 80 | } 81 | this.setData({ 82 | displayStyle 83 | }) 84 | }, 85 | back() { 86 | const data = this.data 87 | if (data.delta) { 88 | wx.navigateBack({ 89 | delta: data.delta 90 | }) 91 | } 92 | this.triggerEvent('back', { delta: data.delta }, {}) 93 | } 94 | } 95 | }) 96 | -------------------------------------------------------------------------------- /examples/product-list/components/navigation-bar/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {}, 4 | "componentFramework": "glass-easel", 5 | "addGlobalClass": true 6 | } -------------------------------------------------------------------------------- /examples/product-list/components/navigation-bar/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {{title}} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/product-list/components/navigation-bar/index.wxss: -------------------------------------------------------------------------------- 1 | .weui-navigation-bar { 2 | overflow: hidden; 3 | color: rgba(0, 0, 0, .9); 4 | width: 100vw; 5 | } 6 | 7 | .weui-navigation-bar__placeholder { 8 | background: #f7f7f7; 9 | position: relative; 10 | } 11 | 12 | .weui-navigation-bar__inner, .weui-navigation-bar__inner .weui-navigation-bar__left { 13 | display: flex; 14 | align-items: center; 15 | flex-direction: row; 16 | } 17 | 18 | .weui-navigation-bar__inner { 19 | position: relative; 20 | padding-right: 95px; 21 | width: 100vw; 22 | box-sizing: border-box; 23 | } 24 | 25 | .weui-navigation-bar__inner .weui-navigation-bar__left { 26 | position: relative; 27 | /* width: 95px; */ 28 | padding-left: 16px; 29 | box-sizing: border-box; 30 | } 31 | 32 | .weui-navigation-bar__btn_goback_wrapper { 33 | padding: 11px 18px 11px 16px; 34 | margin: -11px -18px -11px -16px; 35 | } 36 | 37 | .weui-navigation-bar__inner .weui-navigation-bar__left .weui-navigation-bar__btn_goback { 38 | font-size: 12px; 39 | width: 12px; 40 | height: 24px; 41 | background: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z'/%3E%3C/svg%3E") no-repeat 50% 50%; 42 | background-size: cover; 43 | } 44 | 45 | .weui-navigation-bar__inner .weui-navigation-bar__center { 46 | font-size: 17px; 47 | text-align: center; 48 | position: relative; 49 | flex: 1; 50 | display: flex; 51 | flex-direction: column; 52 | align-items: center; 53 | justify-content: center; 54 | font-weight: bold; 55 | } 56 | 57 | @media(prefers-color-scheme: dark) { 58 | .weui-navigation-bar { 59 | color: hsla(0, 0%, 100%, .8); 60 | } 61 | .weui-navigation-bar__inner { 62 | background-color: #1f1f1f; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/product-list/images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/awesome-skyline/25e2d93c2daf3f78d0908ff53ea305147d0c8978/examples/product-list/images/search.png -------------------------------------------------------------------------------- /examples/product-list/pages/index/index.js: -------------------------------------------------------------------------------- 1 | import { getCategory, getGoods, getVIPCategory } from '../../util' 2 | 3 | const systemInfo = wx.getSystemInfoSync() 4 | 5 | const { shared, Easing } = wx.worklet 6 | 7 | const lerp = function (begin, end, t) { 8 | 'worklet' 9 | return begin + (end - begin) * t 10 | } 11 | 12 | const clamp = function (cur, lowerBound, upperBound) { 13 | 'worklet' 14 | if (cur > upperBound) return upperBound 15 | if (cur < lowerBound) return lowerBound 16 | return cur 17 | } 18 | 19 | Component({ 20 | data: { 21 | goods: getGoods(30), 22 | categorySet: [{ 23 | page: 0, 24 | categorys: getCategory() 25 | }, { 26 | page: 1, 27 | categorys: getCategory().reverse() 28 | }], 29 | paddingTop: 44, 30 | renderer: 'skyline', 31 | vipCategorys: getVIPCategory(), 32 | categoryItemWidth: 0, 33 | intoView: '', 34 | selected: 0 35 | }, 36 | 37 | lifetimes: { 38 | created() { 39 | this.searchBarWidth = shared(100) 40 | this.navBarOpactiy = shared(1) 41 | }, 42 | attached() { 43 | const padding = 10 * 2 44 | const categoryItemWidth = (systemInfo.windowWidth - padding) / 5 45 | this.setData({ categoryItemWidth, paddingTop: systemInfo.statusBarHeight, renderer: this.renderer }) 46 | 47 | this.applyAnimatedStyle('.nav-bar', () => { 48 | 'worklet' 49 | return { 50 | opacity: this.navBarOpactiy.value 51 | } 52 | }) 53 | 54 | this.applyAnimatedStyle('.search', () => { 55 | 'worklet' 56 | return { 57 | width: `${this.searchBarWidth.value}%`, 58 | } 59 | }) 60 | 61 | this.applyAnimatedStyle('.search-container', () => { 62 | 'worklet' 63 | return { 64 | backgroundColor: (this.navBarOpactiy.value > 0 && this.renderer == 'skyline') ? 'transparent' : '#fff' 65 | } 66 | }) 67 | }, 68 | }, 69 | 70 | methods: { 71 | chooseVipCategory(evt) { 72 | const id = evt.currentTarget.dataset.id 73 | this.setData({ 74 | intoView: `vip-category-${id}`, 75 | selected: parseInt(id, 10) 76 | }) 77 | }, 78 | 79 | handleScrollUpdate(evt) { 80 | 'worklet' 81 | const maxDistance = 60 82 | const scrollTop = clamp(evt.detail.scrollTop, 0, maxDistance) 83 | const progress = scrollTop / maxDistance 84 | const EasingFn = Easing.cubicBezier(0.4, 0.0, 0.2, 1.0) 85 | this.searchBarWidth.value = lerp(100, 70, EasingFn(progress)) 86 | this.navBarOpactiy.value = lerp(1, 0, progress) 87 | }, 88 | }, 89 | }) 90 | -------------------------------------------------------------------------------- /examples/product-list/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "navigation-bar": "../../components/navigation-bar" 4 | }, 5 | "disableScroll": true, 6 | "navigationStyle": "custom" 7 | } 8 | -------------------------------------------------------------------------------- /examples/product-list/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 微信学堂 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 这是skyline实现的~ 26 | 搜索 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {{category.name}} 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 60 | 61 | 62 | {{item.name}} 63 | 64 | 65 | 66 | 67 | 74 | 75 | 76 | 77 | 78 | {{item.title}} 79 | 80 | 81 | 3万+评价 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /examples/product-list/pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | .fake-nav-bar { 2 | height: 60px; 3 | } 4 | 5 | .search-container { 6 | padding: 0 16px 10px 16px; 7 | /* margin-top: 44px; */ 8 | /* background-color: transparent; */ 9 | background-color: #FFF; 10 | } 11 | .search { 12 | display: flex; 13 | flex-direction: row; 14 | box-sizing: border-box; 15 | width: 100%; 16 | height: 40px; 17 | border-radius: 20px; 18 | border: 2px solid #07c160; 19 | position: relative; 20 | align-items: center; 21 | background-color: #fff; 22 | } 23 | 24 | .search-text { 25 | color: #8f8888; 26 | font-size: 14px; 27 | } 28 | 29 | .search-icon-wrp { 30 | display: flex; 31 | width: 30px; 32 | height: 100%; 33 | flex-direction: row; 34 | align-items: center; 35 | justify-content: center; 36 | } 37 | 38 | .search-icon { 39 | width: 16px; 40 | height: 16px; 41 | } 42 | 43 | .search-btn { 44 | position: absolute; 45 | right: 0; 46 | width: 60px; 47 | height: 100%; 48 | border-radius: 20px; 49 | background-color: #07c160; 50 | display: flex; 51 | align-items: center; 52 | justify-content: center; 53 | color: #FFF; 54 | font-size: 16px; 55 | /* font-weight: bold; */ 56 | } 57 | 58 | .nav-bar { 59 | background-color: #fff; 60 | position: absolute; 61 | } 62 | 63 | .nav-left { 64 | display: flex; 65 | flex-direction: row; 66 | align-items: center; 67 | } 68 | 69 | .nav-logo { 70 | width: 40px; 71 | height: 40px; 72 | border-radius: 50%; 73 | } 74 | 75 | .nav-title { 76 | margin-left: 2px; 77 | font-size: 20px; 78 | color: #3f3e3e; 79 | /* font-weight: bold; */ 80 | } 81 | 82 | .scroll-area { 83 | height: 100vh; 84 | } 85 | 86 | .category-wrp { 87 | height: 220px; 88 | padding: 10px 0; 89 | background-color: #FFF; 90 | } 91 | 92 | .category-list { 93 | display: flex; 94 | flex-direction: row; 95 | justify-content: space-between; 96 | flex-wrap: wrap; 97 | padding: 0 10px; 98 | } 99 | 100 | .category-item { 101 | display: flex; 102 | flex-direction: column; 103 | align-items: center; 104 | margin: 10px 0; 105 | font-size: 14px; 106 | } 107 | 108 | .category-icon { 109 | width: 50px; 110 | height: 50px; 111 | margin-bottom: 10px; 112 | border-radius: 50%; 113 | } 114 | 115 | .category-name { 116 | height: 30px; 117 | display: flex; 118 | justify-content: center; 119 | align-items: center; 120 | } 121 | 122 | .good { 123 | background-color: #FFF; 124 | border-radius: 6px; 125 | overflow: hidden; 126 | } 127 | 128 | .good-icon { 129 | width: 100%; 130 | } 131 | 132 | .good-title { 133 | width: 100%; 134 | line-height: 1.4; 135 | margin: 8px 0; 136 | padding: 0 5px; 137 | font-size: 14px; 138 | } 139 | 140 | .good-comment { 141 | font-size: 12px; 142 | color: #ccc; 143 | padding-left: 5px; 144 | margin-bottom: 10px; 145 | } 146 | 147 | .vip-categorys-list { 148 | background-color: #fff; 149 | width: 100%; 150 | height: 60px; 151 | display: flex; 152 | flex-direction: row; 153 | } 154 | 155 | .vip-category-item { 156 | display: flex; 157 | flex-shrink: 0; 158 | height: 100%; 159 | justify-content: center; 160 | align-items: center; 161 | padding-right: 20px; 162 | padding-left: 40px; 163 | } 164 | .vip-category-item:first-child { 165 | padding-left: 20px; 166 | } 167 | 168 | .vip-category-name { 169 | color: #8f8888; 170 | font-size: 16px; 171 | transition: transform .3s; 172 | } 173 | 174 | .selected { 175 | transform: scale(1.2); 176 | color: #2c2c2c; 177 | font-weight: bold; 178 | } 179 | -------------------------------------------------------------------------------- /examples/product-list/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appid": "wxe5f52902cf4de896", 3 | "compileType": "miniprogram", 4 | "libVersion": "latest", 5 | "packOptions": { 6 | "ignore": [], 7 | "include": [] 8 | }, 9 | "setting": { 10 | "coverView": true, 11 | "es6": true, 12 | "postcss": true, 13 | "minified": true, 14 | "enhance": true, 15 | "showShadowRootInWxmlPanel": true, 16 | "packNpmRelationList": [], 17 | "babelSetting": { 18 | "ignore": [], 19 | "disablePlugins": [], 20 | "outputPath": "" 21 | }, 22 | "condition": false, 23 | "skylineRenderEnable": true, 24 | "compileWorklet": true 25 | }, 26 | "condition": {}, 27 | "editorSetting": { 28 | "tabIndent": "insertSpaces", 29 | "tabSize": 2 30 | } 31 | } -------------------------------------------------------------------------------- /examples/product-list/project.private.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", 3 | "projectname": "product-list", 4 | "setting": { 5 | "compileHotReLoad": false 6 | }, 7 | "libVersion": "latest" 8 | } -------------------------------------------------------------------------------- /examples/product-list/sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /examples/product-list/util.js: -------------------------------------------------------------------------------- 1 | export function getCategory() { 2 | let categorys = [ 3 | '中小商户', 4 | '商超零售', 5 | '品牌服饰', 6 | '餐饮', 7 | '医疗', 8 | '酒旅', 9 | '政务', 10 | '开发技术', 11 | '产品能力', 12 | '运营规范', 13 | ] 14 | let images = [ 15 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJgc9If4xjgvN3O4UQclWMiJxMoExkarf71FN-3SSf3Sh-GoatfvTbKcPE-grH-1L9g', 16 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJrmxgZKLSYHHwqx6YfJyqPnSNeIHovelr_r6GLFpsiCuCuBgYKBc68vBi0dJYSMeZA', 17 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJk4e_uP2-FWEQKo5Ijp5itIrlf-qIXozTGY6D595Ri2YIoLCUS7YseOda2JLTAEz7Q', 18 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJj6M4Aqf4ov3F7tRlrb62now5owS_Q6vkhsWjnU_uWVbBR84dTHxG4tzAcjwAqOGZQ', 19 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJrBCMpypwJ_xFgSnas6etb7Y6JuMRRMBJ6cSMmbmSkCkOjCSPDdC_eLEK1_FT-d-PQ', 20 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJitcJIfp9P4VSWxi3126XeiyZ2BnnH0xg-oIXAUgHBgaHjBMwxzSjSkEkTMRqzlKZw', 21 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJnw5zq4f_XW3swKAowexqAbziuojU5W9v4CJixA-NDJShkfS0ne3KWY_6SB56yqb3g', 22 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJu8Q1L10fFiMMnZVYnLoP1GuT0q26CJLtjSRfJAjTvj6DBNuWrzzMD9UYZEb-pznKA', 23 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChUzvBckq_FnOrUIIgSP2hJ9cYByUIwgzBEaDCcF5YiPNMmcMg0ewQBn_nMCt-q71vsg', 24 | 'https://res.wx.qq.com/op_res/Zmvv0fisUjaMjuqWLhWWkuzGktaXJEQt46EaKsCKeT06Z4tROseXN0joI7h2qwzqyx2FUy57cveZL-8iArI8_Q', 25 | ] 26 | categorys = categorys.map((name, id) => ({ 27 | id, 28 | name, 29 | icon: images[(id % categorys.length)] //`/images/boy/b${id}.png` 30 | })) 31 | return categorys 32 | } 33 | 34 | export function getGoods(num) { 35 | const titles = [ 36 | '小程序性能优化课程基于实际开发场景,提升小程序性能表现,满足用户体验', 37 | '解析常见小程序违规类型,帮助大家更好理解平台规则', 38 | '快速了解微信小程序在医疗行业的应用', 39 | '小程序直播的企业实践案例。', 40 | '微信客服轻松配置,入门必修', 41 | '想做互联网的生意,可以通过微信怎么经营呢?', 42 | '了解小程序开发动态,听官方为你解读新能力', 43 | '医保支付、互联网医院、线上问诊...小程序如何帮助传统医院数字化?', 44 | '内含开店指引、店铺运营和平台规则,帮你快速掌握小商店经营秘诀', 45 | '浅谈连锁零售的私域流量运营' 46 | ] 47 | 48 | const images = [ 49 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU_LhYxhaP5JTy7TWgezsDY7RW_l_e04fR7oG7sCKmS8hc8mVeZaY6eUWT3nk-ww_ZQ', 50 | 'https://res.wx.qq.com/op_res/Zmvv0fisUjaMjuqWLhWWkuzGktaXJEQt46EaKsCKeT06Z4tROseXN0joI7h2qwzqyx2FUy57cveZL-8iArI8_Q', 51 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJrBCMpypwJ_xFgSnas6etb7Y6JuMRRMBJ6cSMmbmSkCkOjCSPDdC_eLEK1_FT-d-PQ', 52 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU-O3axOjUJGFgutF9Xc1JL1uxXFWYdW85mWG0Zvm5nv7rvP18CJ0q6-RRFM0xWLLog', 53 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU3ywQmrV-rSREDwo0Hp9m7iIZZ7Njvjq_TlOg_0ss0cgQL0pfKOuB2NRpAcwfALxvw', 54 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJgc9If4xjgvN3O4UQclWMiJxMoExkarf71FN-3SSf3Sh-GoatfvTbKcPE-grH-1L9g', 55 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJu8Q1L10fFiMMnZVYnLoP1GuT0q26CJLtjSRfJAjTvj6DBNuWrzzMD9UYZEb-pznKA', 56 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU1GROxmiPIBOCoA5Es44GxjN0KuCQQsoxEH33l05TCgk04n0dssHAIPxIV2ycSlSJA', 57 | 'https://res.wx.qq.com/op_res/RBwYn_b7VGuWuLJ2qBChU68wmBQzYcfQfuIAh1IKWq7OyG0EWxdWGhotYHFh-k-JpmkJ1Otq-mYUT8Dp3iucvg', 58 | 'https://res.wx.qq.com/op_res/mGK9l-4vYzVgHuIz_uFeJrmxgZKLSYHHwqx6YfJyqPnSNeIHovelr_r6GLFpsiCuCuBgYKBc68vBi0dJYSMeZA' 59 | ] 60 | 61 | const goods = [] 62 | for (let id = 0; id < num; id++) { 63 | goods.push({ 64 | id, 65 | title: titles[(id % titles.length)], 66 | icon: images[(id % titles.length)] // `/images/goods/g${(id % num)}.jpg` 67 | }) 68 | } 69 | return goods 70 | } 71 | 72 | export function getVIPCategory() { 73 | let vipCategorys = [ 74 | '本月最热', 75 | '官方经营', 76 | '行业实践', 77 | '微信服务商' 78 | ] 79 | vipCategorys = vipCategorys.map((name, id) => ({ 80 | id, 81 | name 82 | })) 83 | return vipCategorys 84 | } -------------------------------------------------------------------------------- /examples/refresher-two-level/app.js: -------------------------------------------------------------------------------- 1 | // app.js 2 | App({}) 3 | -------------------------------------------------------------------------------- /examples/refresher-two-level/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "index/index" 4 | ], 5 | "window": { 6 | "backgroundTextStyle": "light", 7 | "navigationBarBackgroundColor": "#fff", 8 | "navigationBarTitleText": "Weixin", 9 | "navigationBarTextStyle": "black" 10 | }, 11 | "style": "v2", 12 | "renderer": "skyline", 13 | "rendererOptions": { 14 | "skyline": { 15 | "defaultDisplayBlock": true, 16 | "disableABTest": true, 17 | "sdkVersionBegin": "3.0.0", 18 | "sdkVersionEnd": "15.255.255" 19 | } 20 | }, 21 | "lazyCodeLoading": "requiredComponents", 22 | "componentFramework": "glass-easel", 23 | "sitemapLocation": "sitemap.json" 24 | } -------------------------------------------------------------------------------- /examples/refresher-two-level/app.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/awesome-skyline/25e2d93c2daf3f78d0908ff53ea305147d0c8978/examples/refresher-two-level/app.wxss -------------------------------------------------------------------------------- /examples/refresher-two-level/components/navigation-bar/index.js: -------------------------------------------------------------------------------- 1 | Component({ 2 | options: { 3 | multipleSlots: true // 在组件定义时的选项中启用多slot支持 4 | }, 5 | /** 6 | * 组件的属性列表 7 | */ 8 | properties: { 9 | extClass: { 10 | type: String, 11 | value: '' 12 | }, 13 | title: { 14 | type: String, 15 | value: '' 16 | }, 17 | background: { 18 | type: String, 19 | value: '' 20 | }, 21 | color: { 22 | type: String, 23 | value: '' 24 | }, 25 | back: { 26 | type: Boolean, 27 | value: true 28 | }, 29 | loading: { 30 | type: Boolean, 31 | value: false 32 | }, 33 | animated: { 34 | // 显示隐藏的时候opacity动画效果 35 | type: Boolean, 36 | value: true 37 | }, 38 | show: { 39 | // 显示隐藏导航,隐藏的时候navigation-bar的高度占位还在 40 | type: Boolean, 41 | value: true, 42 | observer: '_showChange' 43 | }, 44 | // back为true的时候,返回的页面深度 45 | delta: { 46 | type: Number, 47 | value: 1 48 | } 49 | }, 50 | /** 51 | * 组件的初始数据 52 | */ 53 | data: { 54 | displayStyle: '' 55 | }, 56 | attached() { 57 | const rect = wx.getMenuButtonBoundingClientRect() 58 | wx.getSystemInfo({ 59 | success: (res) => { 60 | this.setData({ 61 | statusBarHeight: res.statusBarHeight, 62 | innerPaddingRight: `padding-right:${res.windowWidth - rect.left}px`, 63 | leftWidth: `width:${res.windowWidth - rect.left}px`, 64 | navBarHeight: rect.bottom + rect.top - res.statusBarHeight, 65 | }) 66 | } 67 | }) 68 | }, 69 | /** 70 | * 组件的方法列表 71 | */ 72 | methods: { 73 | _showChange(show) { 74 | const animated = this.data.animated 75 | let displayStyle = '' 76 | if (animated) { 77 | displayStyle = `opacity: ${show ? '1' : '0'};transition: opacity 0.5s;` 78 | } else { 79 | displayStyle = `display: ${show ? '' : 'none'}` 80 | } 81 | this.setData({ 82 | displayStyle 83 | }) 84 | }, 85 | back() { 86 | const data = this.data 87 | if (data.delta) { 88 | wx.navigateBack({ 89 | delta: data.delta 90 | }) 91 | } 92 | this.triggerEvent('back', { delta: data.delta }, {}) 93 | } 94 | } 95 | }) 96 | -------------------------------------------------------------------------------- /examples/refresher-two-level/components/navigation-bar/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {}, 4 | "componentFramework": "glass-easel", 5 | "addGlobalClass": true 6 | } -------------------------------------------------------------------------------- /examples/refresher-two-level/components/navigation-bar/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {{title}} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/refresher-two-level/components/navigation-bar/index.wxss: -------------------------------------------------------------------------------- 1 | .weui-navigation-bar { 2 | overflow: hidden; 3 | color: rgba(0, 0, 0, .9); 4 | width: 100vw; 5 | } 6 | 7 | .weui-navigation-bar__placeholder { 8 | background: #f7f7f7; 9 | position: relative; 10 | } 11 | 12 | .weui-navigation-bar__inner, .weui-navigation-bar__inner .weui-navigation-bar__left { 13 | display: flex; 14 | align-items: center; 15 | flex-direction: row; 16 | } 17 | 18 | .weui-navigation-bar__inner { 19 | position: relative; 20 | padding-right: 95px; 21 | width: 100vw; 22 | box-sizing: border-box; 23 | } 24 | 25 | .weui-navigation-bar__inner .weui-navigation-bar__left { 26 | position: relative; 27 | /* width: 95px; */ 28 | padding-left: 16px; 29 | box-sizing: border-box; 30 | } 31 | 32 | .weui-navigation-bar__btn_goback_wrapper { 33 | padding: 11px 18px 11px 16px; 34 | margin: -11px -18px -11px -16px; 35 | } 36 | 37 | .weui-navigation-bar__inner .weui-navigation-bar__left .weui-navigation-bar__btn_goback { 38 | font-size: 12px; 39 | width: 12px; 40 | height: 24px; 41 | background: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z'/%3E%3C/svg%3E") no-repeat 50% 50%; 42 | background-size: cover; 43 | } 44 | 45 | .weui-navigation-bar__inner .weui-navigation-bar__center { 46 | font-size: 17px; 47 | text-align: center; 48 | position: relative; 49 | flex: 1; 50 | display: flex; 51 | flex-direction: column; 52 | align-items: center; 53 | justify-content: center; 54 | font-weight: bold; 55 | } 56 | 57 | @media(prefers-color-scheme: dark) { 58 | .weui-navigation-bar { 59 | color: hsla(0, 0%, 100%, .8); 60 | } 61 | .weui-navigation-bar__inner { 62 | background-color: #1f1f1f; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/refresher-two-level/goods.less: -------------------------------------------------------------------------------- 1 | .exp-room { 2 | .nav-bar { 3 | background-color: #fff; 4 | position: absolute; 5 | } 6 | 7 | .nav-left { 8 | display: flex; 9 | flex-direction: row; 10 | align-items: center; 11 | } 12 | 13 | .nav-logo { 14 | width: 40px; 15 | height: 40px; 16 | border-radius: 50%; 17 | } 18 | 19 | .nav-title { 20 | margin-left: 2px; 21 | font-size: 20px; 22 | color: #3f3e3e; 23 | } 24 | 25 | width: 100vw; 26 | height: 100vh; 27 | background-color: rgb(3, 3, 41); 28 | 29 | .nav-bar { 30 | position: relative; 31 | background-color: rgb(3, 3, 41); 32 | } 33 | 34 | .nav-logo { 35 | width: 32px; 36 | height: 32px; 37 | } 38 | 39 | .nav-title { 40 | color: #fff; 41 | font-size: 18px; 42 | } 43 | 44 | .exp-category-list { 45 | display: flex; 46 | flex-direction: row; 47 | margin: 10px 16px; 48 | } 49 | 50 | .exp-category-item { 51 | margin-right: 20px; 52 | } 53 | 54 | .exp-category-name { 55 | color: #8f8888; 56 | font-size: 16px; 57 | } 58 | 59 | .selected { 60 | transform: scale(1.2); 61 | color: #fff; 62 | font-weight: bold; 63 | } 64 | 65 | .scroll-area { 66 | flex: 1; 67 | } 68 | 69 | .video-container { 70 | height: 240px; 71 | } 72 | 73 | .video { 74 | overflow: hidden; 75 | border-radius: 16px; 76 | margin: 10px 0; 77 | position: relative; 78 | width: 100%; 79 | height: 100%; 80 | } 81 | 82 | .video-title { 83 | position: absolute; 84 | top: 16px; 85 | left: 16px; 86 | color: #fff; 87 | font-size: 14px; 88 | } 89 | 90 | .expand { 91 | width: 100%; 92 | height: 100%; 93 | } 94 | 95 | .refresher-tips { 96 | position: absolute; 97 | bottom: 20px; 98 | width: 100%; 99 | text-align: center; 100 | color: #fff; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/refresher-two-level/goods.wxml: -------------------------------------------------------------------------------- 1 | 39 | 40 | -------------------------------------------------------------------------------- /examples/refresher-two-level/goods/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "navigation-bar": "../components/navigation-bar" 4 | }, 5 | "renderer": "skyline", 6 | "componentFramework": "glass-easel" 7 | } -------------------------------------------------------------------------------- /examples/refresher-two-level/goods/index.less: -------------------------------------------------------------------------------- 1 | @import '../goods.less'; -------------------------------------------------------------------------------- /examples/refresher-two-level/goods/index.ts: -------------------------------------------------------------------------------- 1 | import { getCategory, getGoods, getVIPCategory, getExpCategory, getVideoList } from '../util' 2 | 3 | // pages/goods/index.ts 4 | Page({ 5 | 6 | /** 7 | * 页面的初始数据 8 | */ 9 | data: { 10 | goodsData: { 11 | expSelected: 0, 12 | expCategorys: getExpCategory(), 13 | videoList: getVideoList(20), 14 | hasRouteDone: false, 15 | } 16 | }, 17 | 18 | back() { 19 | wx.navigateBack({}) 20 | }, 21 | 22 | /** 23 | * 生命周期函数--监听页面加载 24 | */ 25 | 26 | onRouteDone() { 27 | console.info('@@@ goods page routeDone ') 28 | this.setData({ 29 | 'goodsData.hasRouteDone': true, 30 | }) 31 | if (this.eventChannel) { 32 | this.eventChannel.emit('nextPageRouteDone', { }); 33 | } 34 | }, 35 | 36 | onLoad() { 37 | this.eventChannel = this.getOpenerEventChannel() 38 | }, 39 | 40 | /** 41 | * 生命周期函数--监听页面初次渲染完成 42 | */ 43 | onReady() { 44 | 45 | }, 46 | 47 | /** 48 | * 生命周期函数--监听页面显示 49 | */ 50 | onShow() { 51 | 52 | }, 53 | 54 | /** 55 | * 生命周期函数--监听页面隐藏 56 | */ 57 | onHide() { 58 | 59 | }, 60 | 61 | /** 62 | * 生命周期函数--监听页面卸载 63 | */ 64 | onUnload() { 65 | 66 | }, 67 | 68 | /** 69 | * 页面相关事件处理函数--监听用户下拉动作 70 | */ 71 | onPullDownRefresh() { 72 | 73 | }, 74 | 75 | /** 76 | * 页面上拉触底事件的处理函数 77 | */ 78 | onReachBottom() { 79 | 80 | }, 81 | 82 | /** 83 | * 用户点击右上角分享 84 | */ 85 | onShareAppMessage() { 86 | 87 | } 88 | }) -------------------------------------------------------------------------------- /examples/refresher-two-level/goods/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 |